Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: token page #14

Draft
wants to merge 174 commits into
base: feat-ccip-landing
Choose a base branch
from
Draft
Changes from all commits
Commits
Show all changes
174 commits
Select commit Hold shift + click to select a range
960ce6a
Add product metrics (#2089)
khadni Sep 24, 2024
960fc35
Simplify selected network button style (#2091)
khadni Sep 24, 2024
38daeaa
Node release 2.16.0 (#2085)
thedriftofwords Sep 24, 2024
2728ccf
update (#2087)
aelmanaa Sep 25, 2024
a61523f
Feat: token page
markshenouda Sep 26, 2024
484e6c1
fix: search
markshenouda Sep 26, 2024
ef3c4c0
fix: show correct lanes
markshenouda Sep 26, 2024
ebdfd93
fix: chainhero search
markshenouda Sep 26, 2024
91c13b4
feat: added total lanes and total networks
markshenouda Sep 26, 2024
04a3408
fix: show the correct chains for each network
markshenouda Sep 26, 2024
7bbe3c3
Add GitHub & page edit links (#2094)
khadni Sep 26, 2024
946a40e
Add heartbeat latency info (#2095)
thedriftofwords Sep 27, 2024
f414060
CLA Base mainnet (#2096)
thedriftofwords Sep 30, 2024
68c85cd
Add quick links ID attributes (#2099)
khadni Oct 1, 2024
f227194
feat: Drawer & filters
markshenouda Oct 1, 2024
72d477b
First draft to move page out
Zelig880 Oct 2, 2024
a3370cf
Developer Responsibilities update (#2102)
khadni Oct 2, 2024
7751183
feat: API integrations
markshenouda Oct 3, 2024
c2812f7
remove unused files
markshenouda Oct 3, 2024
159a417
mock
aelmanaa Sep 6, 2024
2e88e09
mock
aelmanaa Sep 6, 2024
b4ee80a
get-status API vercel function
aelmanaa Aug 28, 2024
0046d74
fix caching
aelmanaa Sep 4, 2024
f887782
cleanup
aelmanaa Sep 6, 2024
b0b6fc0
update
aelmanaa Oct 3, 2024
f309d7b
update
aelmanaa Oct 3, 2024
e0ee72c
update
aelmanaa Oct 3, 2024
bd731a0
Merge remote-tracking branch 'thisdot/feat-ccip-landing-token' into s…
Zelig880 Oct 3, 2024
b7f8240
Move token and Chains
Zelig880 Oct 3, 2024
2ff4815
Fix search links
Zelig880 Oct 3, 2024
e687eb9
Merge remote-tracking branch 'origin/ccip/config-redesign' into feat-…
Zelig880 Oct 3, 2024
d42e152
Fix chain after Merge
Zelig880 Oct 3, 2024
34ba959
Streams onboarding clarification (#2101)
khadni Oct 3, 2024
65a00d5
Fix shadow on navigation (#2100)
Zelig880 Oct 3, 2024
b6e1b93
Adjust VRF Sepolia lane and CLA deprecation notice (#2103)
thedriftofwords Oct 3, 2024
51d3c96
Fix URL from main navigation
Zelig880 Oct 4, 2024
42bc3e6
Rename heading
Zelig880 Oct 4, 2024
4ccbd17
Fix token passing wrong environment
Zelig880 Oct 4, 2024
58332d3
CCIP 1.5 (#2104)
aelmanaa Oct 4, 2024
0040ecf
mock
aelmanaa Sep 6, 2024
b690455
mock
aelmanaa Sep 6, 2024
e2f8d8b
get-status API vercel function
aelmanaa Aug 28, 2024
114cabb
fix caching
aelmanaa Sep 4, 2024
f751c61
cleanup
aelmanaa Sep 6, 2024
462529d
update
aelmanaa Oct 3, 2024
f22043c
update
aelmanaa Oct 3, 2024
7653c75
update
aelmanaa Oct 4, 2024
fb1f1fa
Add gas spikes note (#2105)
khadni Oct 4, 2024
69ac54f
feat: Drawer Mobile close button
markshenouda Oct 7, 2024
e8c662f
feat: hook tables to seach and filters
markshenouda Oct 7, 2024
f83360e
feat: lane drawer
markshenouda Oct 7, 2024
95a9707
remove unused console.logs
markshenouda Oct 7, 2024
86f2f0f
Fix Testnet token not working
Zelig880 Oct 7, 2024
f80e304
Merge remote-tracking branch 'origin/ccip/config-redesign' into feat-…
Zelig880 Oct 7, 2024
ac2b398
Add CCIP Directory on sidebar
Zelig880 Oct 7, 2024
7827c35
Include missing import
Zelig880 Oct 7, 2024
cba0693
Merge remote-tracking branch 'origin/main' into feat-ccip-landing-token
Zelig880 Oct 7, 2024
0d8f738
Fix component import
Zelig880 Oct 7, 2024
dc09606
Fix component import
Zelig880 Oct 7, 2024
020a891
Fix component import
Zelig880 Oct 7, 2024
a9747ad
update (#2110)
aelmanaa Oct 7, 2024
cb1ad68
update (#2108)
aelmanaa Oct 7, 2024
89fbf2d
new integration (#2111)
aelmanaa Oct 8, 2024
643822b
fix: lane search
markshenouda Oct 10, 2024
3c025d0
Add Search Overlay
Zelig880 Oct 10, 2024
f93ed46
fix: breadcrumb
markshenouda Oct 10, 2024
0b1dcfd
fix: search styles
markshenouda Oct 10, 2024
82409ce
feat: add search to token
markshenouda Oct 10, 2024
17be6dd
fix: search + tokens operational
markshenouda Oct 10, 2024
0e6a56b
fix: lane drawer spacing
markshenouda Oct 10, 2024
d9a8b5f
feat: copy value component
markshenouda Oct 10, 2024
fed5d86
fix: copy value component
markshenouda Oct 10, 2024
bd2170b
Multiple Changes
Zelig880 Oct 10, 2024
a624c1b
scroll (#2114)
aelmanaa Oct 10, 2024
a8e4e2e
add Scroll ccip to QL (#2115)
khadni Oct 10, 2024
112ad49
nit (#2116)
khadni Oct 10, 2024
cb8faa1
nit (#2117)
khadni Oct 10, 2024
b3d60a9
mock
aelmanaa Sep 6, 2024
5901602
mock
aelmanaa Sep 6, 2024
d588026
get-status API vercel function
aelmanaa Aug 28, 2024
78ce139
fix caching
aelmanaa Sep 4, 2024
6c373c3
cleanup
aelmanaa Sep 6, 2024
31d151e
update
aelmanaa Oct 3, 2024
c464f28
update
aelmanaa Oct 3, 2024
1a97146
update
aelmanaa Oct 4, 2024
693c446
Merge remote-tracking branch 'origin/ccip/config-redesign' into feat-…
Zelig880 Oct 11, 2024
444784e
Add Link for the "add my token" button
Zelig880 Oct 11, 2024
b2fb00a
rmn status (#2118)
aelmanaa Oct 11, 2024
43cdd7d
fix: token drawer
markshenouda Oct 11, 2024
45b3a66
fix: token tabs
markshenouda Oct 11, 2024
0058fc6
feat: fee tokens
markshenouda Oct 11, 2024
70a8b19
add issuer por field (#2119)
khadni Oct 11, 2024
b2ae5f1
Node release 2.17.0 (#2121)
thedriftofwords Oct 11, 2024
96b05c0
chains metadata updated (#2122)
github-actions[bot] Oct 14, 2024
9ccb133
Various update from Review
Zelig880 Oct 14, 2024
decfa0a
Merge remote-tracking branch 'origin/main' into feat-ccip-landing-token
Zelig880 Oct 14, 2024
de5f8c8
Add RMN
Zelig880 Oct 14, 2024
9c68765
CCIP GHO fee token (#2120)
aelmanaa Oct 14, 2024
f39b625
Fix some of the URLs being incorrectly set
Zelig880 Oct 15, 2024
1cbce3c
Fix build
Zelig880 Oct 15, 2024
0f77598
Fix center align on build
Zelig880 Oct 15, 2024
8051f37
update rmn disclaimer (#2124)
aelmanaa Oct 15, 2024
623bc41
Header transparency (#2125)
Zelig880 Oct 15, 2024
e2a1583
Apply multiple changes from review
Zelig880 Oct 15, 2024
bf1ce57
Add search on mobile (#2126)
Zelig880 Oct 16, 2024
cb2bf94
Enable RMN and Fix Address URL
Zelig880 Oct 16, 2024
0cda512
Add Native token on feeToken
Zelig880 Oct 16, 2024
60a9251
Add Data typees for Josh
Zelig880 Oct 16, 2024
10fb64d
Draft for Network operation
Zelig880 Oct 16, 2024
7a6117b
Fix ExplorerUrl
Zelig880 Oct 16, 2024
a018093
Fix TS issue
Zelig880 Oct 16, 2024
30f50ce
clarify note (#2128)
aelmanaa Oct 16, 2024
0ff7522
Add tooltips
Zelig880 Oct 17, 2024
2d540df
Add General icons (#2127)
Zelig880 Oct 18, 2024
df0e5d2
Merge branch 'main; into UpdateIcons
Zelig880 Oct 21, 2024
5d9be4f
Fix Typo in tooltip
Zelig880 Oct 21, 2024
612c58e
Add Tooltips
Zelig880 Oct 21, 2024
b0cfb82
Merge branch 'Network-operation' into feat-ccip-landing-token
Zelig880 Oct 21, 2024
a1d4009
fix: Update "list my token"'s url (#15)
markshenouda Oct 21, 2024
2126024
fix: Heading colors and other minor css issues (#20)
markshenouda Oct 21, 2024
3ae3de6
update (#2129)
aelmanaa Oct 21, 2024
d37f1d9
fix: Page width incorrect (DocsLayout grid) (#19)
markshenouda Oct 21, 2024
8e2c7d0
mock
aelmanaa Sep 6, 2024
04262bc
mock
aelmanaa Sep 6, 2024
c49c83c
get-status API vercel function
aelmanaa Aug 28, 2024
2021664
fix caching
aelmanaa Sep 4, 2024
8d30125
cleanup
aelmanaa Sep 6, 2024
808e47e
update
aelmanaa Oct 3, 2024
7d685ce
update
aelmanaa Oct 3, 2024
cea41b4
update
aelmanaa Oct 4, 2024
57782ba
update
aelmanaa Oct 21, 2024
006f4d3
Merge remote-tracking branch 'origin/ccip/config-redesign' into feat-…
Zelig880 Oct 21, 2024
8bf5153
Fix: Drawer (#26)
markshenouda Oct 22, 2024
1bc02c1
fix: Removing the grid banner (#25)
markshenouda Oct 22, 2024
1e6afdb
fix: icon sizes & use css variables (#24)
markshenouda Oct 22, 2024
d55170c
Fix Fee Token URL (#17)
Zelig880 Oct 22, 2024
744dc6d
Use Symbol instead than name for Native Gas Token (#16)
Zelig880 Oct 22, 2024
0cbb30d
Filter out FeeTokenOnly (#18)
Zelig880 Oct 22, 2024
beb1543
Fix: spacing address row (#22)
markshenouda Oct 22, 2024
01c0461
fix: Header spacing (#23)
markshenouda Oct 22, 2024
aa0bc2a
ccip usdm (#2132)
aelmanaa Oct 22, 2024
779fa18
EOA clarification (#2130)
thedriftofwords Oct 22, 2024
33f3996
soneium minato (#2133)
aelmanaa Oct 22, 2024
55e4612
fix: Single/Plural words (#21)
markshenouda Oct 22, 2024
bc07649
fix: Align Networks/Tokens/lanes Alphabetically (#27)
markshenouda Oct 22, 2024
d6edbf7
Navigation buttons and Backdrop blur
Zelig880 Oct 22, 2024
3106322
Merge remote-tracking branch 'origin/main' into feat-ccip-landing-token
Zelig880 Oct 23, 2024
847ac15
fallback icon
aelmanaa Oct 23, 2024
4464d95
Merge remote-tracking branch 'origin/ccip/config-redesign' into feat-…
Zelig880 Oct 23, 2024
d17a2af
Improve sizing of Chain cards (#28)
Zelig880 Oct 23, 2024
bad0fa1
Improve Image Fallback (#31)
Zelig880 Oct 23, 2024
e7854ca
Add extra SEO
Zelig880 Oct 24, 2024
7a15190
Fix 340 (#32)
markshenouda Oct 24, 2024
61a9b4f
Fix token search
Zelig880 Oct 23, 2024
c717900
Padding and spaces changes
Zelig880 Oct 24, 2024
bfa7b60
Feat: Add see more, CSS fixes (#36)
markshenouda Oct 24, 2024
63db402
Review from Nav
Zelig880 Oct 24, 2024
48a2489
Update Tooltio
Zelig880 Oct 25, 2024
19c9ff4
fix: Breadcrum + page titles (#37)
markshenouda Oct 25, 2024
1d9bd1b
Update Page to be "directory" instead than "supported-networks" (#29)
Zelig880 Oct 25, 2024
d69f21b
Remove hardcoded URL
Zelig880 Oct 28, 2024
61c022d
Update link verbiage for security reason
Zelig880 Oct 28, 2024
fa5f7b0
Improve logic and UI for Operational API
Zelig880 Oct 28, 2024
9acd2c1
Update API to use Astro URL
Zelig880 Oct 28, 2024
710f63e
fix: lane drawer on main page (#39)
markshenouda Oct 28, 2024
6f6d717
Move statuses to be client side only
Zelig880 Oct 29, 2024
1922675
Fix lane key
Zelig880 Oct 29, 2024
5a8bc49
Update capacity to show N/A instead than 0
Zelig880 Oct 29, 2024
93a744b
Add EnsureOutOfOrder
Zelig880 Oct 29, 2024
71c692f
Add OpenGraph image
Zelig880 Oct 29, 2024
13a977c
Add Get certified on navbar
Zelig880 Oct 29, 2024
47f48a6
Remove double Drawer
Zelig880 Oct 29, 2024
7226d3b
Change default state to status unavailable
Zelig880 Oct 29, 2024
63d2df2
Fix testnet case issue
Zelig880 Oct 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions astro.config.ts
Original file line number Diff line number Diff line change
@@ -13,6 +13,12 @@ import yaml from "@rollup/plugin-yaml"
// https://astro.build/config
export default defineConfig({
site: "https://docs.chain.link",
redirects: {
"/ccip/directory": "/ccip/directory/mainnet",
"/ccip/supported-networks": "/ccip/directory/mainnet",
"/getting-started": "/getting-started/conceptual-overview",
"/resources": "/resources/link-token-contracts",
},
integrations: [
preact({
include: ["**/preact/*"],
1 change: 1 addition & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ const config: HardhatUserConfig = {
{ version: "0.8.16" },
{ version: "0.8.19" },
{ version: "0.8.20" },
{ version: "0.8.24" },
],
},
}
11,159 changes: 6,358 additions & 4,801 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -37,12 +37,12 @@
"@astrojs/partytown": "^2.1.2",
"@astrojs/preact": "^3.5.2",
"@astrojs/prism": "^3.1.0",
"@chainlink/components": "^0.4.18",
"@astrojs/react": "^3.6.2",
"@astrojs/sitemap": "^3.1.6",
"@astrojs/vercel": "^7.8.0",
"@chainlink/components": "^0.4.16",
"@chainlink/contracts": "1.2.0",
"@chainlink/contracts-ccip": "1.4.0",
"@chainlink/contracts-ccip": "1.5.0-beta.0",
"@chainlink/design-system": "^0.2.8",
"@chainlink/local": "^0.2.1",
"@chainlink/solana-sdk": "^0.2.2",
Binary file removed public/assets/ccip.png
Binary file not shown.
3 changes: 3 additions & 0 deletions public/assets/icons/ccip-cursed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/assets/icons/ccip-degraded.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/assets/icons/ccip-maintenance.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/assets/icons/ccip-operational.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions public/assets/icons/generic-token.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
113 changes: 113 additions & 0 deletions public/changelog.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,117 @@
[
{
"category": "integration",
"changes": [],
"date": "2024-10-22",
"description": "Chainlink Functions is available on Soneium Minato testnet. Visit the [Supported Networks](https://docs.chain.link/chainlink-functions/supported-networks#soneium-minato-testnet) page for more information.",
"relatedNetworks": ["soneium"],
"relatedTokens": [],
"title": "Functions on Soneium Minato",
"topic": "functions",
"urls": []
},
{
"category": "integration",
"changes": [],
"date": "2024-10-22",
"description": "Chainlink VRF 2.5 is available on Soneium Minato testnet. Visit the [Supported Networks](https://docs.chain.link/vrf/v2-5/supported-networks#soneium-minato-testnet) page for more information.",
"relatedNetworks": ["soneium"],
"relatedTokens": [],
"title": "VRF 2.5 on Soneium Minato",
"topic": "vrf",
"urls": []
},
{
"category": "integration",
"changes": [],
"date": "2024-10-22",
"description": "Chainlink CCIP is available on Soneium Minato testnet. Visit the [Supported Networks](https://docs.chain.link/ccip/supported-networks/v1_2_0/testnet) page for more information.",
"relatedNetworks": ["soneium"],
"relatedTokens": [],
"title": "CCIP on Soneium Minato",
"topic": "ccip",
"urls": []
},
{
"category": "integration",
"changes": [],
"date": "2024-10-22",
"description": "Chainlink Data Streams is available in Early Access on Soneium Minato. The verifier proxy address and feed IDs are available on the [Data Streams Feed IDs](https://docs.chain.link/data-streams/stream-ids) page.",
"relatedNetworks": ["soneium"],
"relatedTokens": [],
"title": "Data Streams on Soneium Minato",
"topic": "data",
"subTopic": "data-streams",
"urls": []
},
{
"category": "release",
"changes": [],
"date": "2024-10-10",
"description": "Chainlink Node v2.17.0 is now available. See the [Release Notes](https://github.com/smartcontractkit/chainlink/releases/tag/v2.17.0) for details.",
"relatedNetworks": [],
"relatedTokens": [],
"subTopic": "nodes",
"title": "Chainlink Node v2.17.0",
"topic": "general",
"urls": []
},
{
"category": "integration",
"changes": [],
"date": "2024-10-10",
"description": "Chainlink CCIP is publicly available on Scroll mainnet and testnet.\n\n- Visit the [Supported Networks](https://docs.chain.link/ccip/supported-networks) page for more information.",
"relatedNetworks": ["scroll"],
"relatedTokens": [],
"title": "CCIP on Scroll",
"topic": "ccip",
"urls": []
},
{
"category": "integration",
"changes": [],
"date": "2024-10-08",
"description": "Chainlink CCIP is publicly available on Linea mainnet and testnet.\n\n- Visit the [Supported Networks](https://docs.chain.link/ccip/supported-networks) page for more information.",
"relatedNetworks": ["linea"],
"relatedTokens": [],
"title": "CCIP on Linea",
"topic": "ccip",
"urls": []
},
{
"category": "release",
"changes": [],
"date": "2024-10-04",
"description": "Chainlink CCIP 1.5 is now available on testnet, introducing several new features and enhancements.\n\n**Risk Management Network Coverage:**\nCertain CCIP integrations may not initially include the Risk Management Network (RMN). Blockchains can be integrated with CCIP in a phased approach, starting with the deployment of the Committing and Executing Decentralized Oracle Networks (DONs), followed by the addition of the Risk Management Network in a subsequent update. During a phased deployment, the relevant Commit Stores are configured in the Risk Management contract to always be considered blessed until the Risk Management Network has been deployed for that blockchain. Please refer to the [Supported Networks](https://docs.chain.link/ccip/supported-networks) documentation to identify which integrations utilize a phased approach, and review the [CCIP Service Responsibility](https://docs.chain.link/ccip/service-responsibility) for more information.\n\n**New Version of `EVMExtraArgs`:**\nChainlink CCIP 1.5 introduces a new version of `EVMExtraArgs`, allowing users to set the `allowOutOfOrderExecution` parameter. This feature enables developers to control the execution order of their messages on the destination blockchain. The `allowOutOfOrderExecution` parameter is part of [`EVMExtraArgsV2`](https://docs.chain.link/ccip/api-reference/client#evmextraargsv2) and is available only on lanes where the **Out of Order Execution** property is set to **Optional** or **Required**. Please consult the [Supported Networks page](https://docs.chain.link/ccip/supported-networks) to determine if your target lane supports this feature.",
"relatedNetworks": [],
"relatedTokens": [],
"title": "Chainlink CCIP 1.5 - Testnet",
"topic": "ccip",
"urls": []
},
{
"category": "integration",
"changes": [],
"date": "2024-09-27",
"description": "Chainlink Automation is live on [Base mainnet](https://docs.chain.link/chainlink-automation/overview/supported-networks#base-mainnet).",
"relatedNetworks": ["base"],
"relatedTokens": [],
"title": "Automation on Base mainnet",
"topic": "automation",
"urls": []
},
{
"category": "release",
"changes": [],
"date": "2024-09-23",
"description": "Chainlink Node v2.16.0 is now available. See the [Release Notes](https://github.com/smartcontractkit/chainlink/releases/tag/v2.16.0/) for details.",
"relatedNetworks": [],
"relatedTokens": [],
"subTopic": "nodes",
"title": "Chainlink Node v2.16.0",
"topic": "general",
"urls": []
},
{
"category": "integration",
"changes": [],
Binary file added public/files/ccip-directory.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions public/samples/CCIP/Acknowledger.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
pragma solidity 0.8.24;

import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
@@ -122,7 +122,10 @@ contract Acknowledger is CCIPReceiver, OwnerIsCreator {
tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array aas no tokens are transferred
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit
Client.EVMExtraArgsV1({gasLimit: 200_000})
Client.EVMExtraArgsV2({
gasLimit: 200_000,
allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages from the same sender
})
),
// Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
feeToken: address(s_linkToken)
7 changes: 5 additions & 2 deletions public/samples/CCIP/MessageTracker.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
pragma solidity 0.8.24;

import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
@@ -258,7 +258,10 @@ contract MessageTracker is CCIPReceiver, OwnerIsCreator {
tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array as no tokens are transferred
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit
Client.EVMExtraArgsV1({gasLimit: 300_000})
Client.EVMExtraArgsV2({
gasLimit: 300_000,
allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages from the same sender
})
),
// Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
feeToken: _feeTokenAddress
7 changes: 5 additions & 2 deletions public/samples/CCIP/Messenger.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
pragma solidity 0.8.24;

import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
@@ -261,7 +261,10 @@ contract Messenger is CCIPReceiver, OwnerIsCreator {
tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array as no tokens are transferred
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit
Client.EVMExtraArgsV1({gasLimit: 200_000})
Client.EVMExtraArgsV2({
gasLimit: 200_000, // Gas limit for the callback on the destination chain
allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages from the same sender
})
),
// Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
feeToken: _feeTokenAddress
7 changes: 5 additions & 2 deletions public/samples/CCIP/ProgrammableDefensiveTokenTransfers.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
pragma solidity 0.8.24;

import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
@@ -494,7 +494,10 @@ contract ProgrammableDefensiveTokenTransfers is CCIPReceiver, OwnerIsCreator {
tokenAmounts: tokenAmounts, // The amount and type of token being transferred
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit
Client.EVMExtraArgsV1({gasLimit: 400_000})
Client.EVMExtraArgsV2({
gasLimit: 400_000, // Gas limit for the callback on the destination chain
allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages from the same sender
})
),
// Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
feeToken: _feeTokenAddress
7 changes: 5 additions & 2 deletions public/samples/CCIP/ProgrammableTokenTransfers.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
pragma solidity 0.8.24;

import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
@@ -342,7 +342,10 @@ contract ProgrammableTokenTransfers is CCIPReceiver, OwnerIsCreator {
tokenAmounts: tokenAmounts, // The amount and type of token being transferred
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit
Client.EVMExtraArgsV1({gasLimit: 200_000})
Client.EVMExtraArgsV2({
gasLimit: 200_000, // Gas limit for the callback on the destination chain
allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages from the same sender
})
),
// Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
feeToken: _feeTokenAddress
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
pragma solidity 0.8.24;

import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
@@ -157,7 +157,10 @@ contract ProgrammableTokenTransfersLowGasLimit is CCIPReceiver, OwnerIsCreator {
tokenAmounts: tokenAmounts, // The amount and type of token being transferred
extraArgs: Client._argsToBytes(
// gasLimit set to 20_000 on purpose to force the execution to fail on the destination chain
Client.EVMExtraArgsV1({gasLimit: 20_000})
Client.EVMExtraArgsV2({
gasLimit: 20_000, // Gas limit for the callback on the destination chain
allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages from the same sender
})
),
// Set the feeToken to a LINK token address
feeToken: address(s_linkToken)
2 changes: 1 addition & 1 deletion public/samples/CCIP/Receiver.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
pragma solidity 0.8.24;

import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";
7 changes: 5 additions & 2 deletions public/samples/CCIP/Sender.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
pragma solidity 0.8.24;

import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
@@ -57,7 +57,10 @@ contract Sender is OwnerIsCreator {
tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array indicating no tokens are being sent
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit
Client.EVMExtraArgsV1({gasLimit: 200_000})
Client.EVMExtraArgsV2({
gasLimit: 200_000, // Gas limit for the callback on the destination chain
allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages from the same sender
})
),
// Set the feeToken address, indicating LINK will be used for fees
feeToken: address(s_linkToken)
2 changes: 1 addition & 1 deletion public/samples/CCIP/TestCCIPLocalSimulator.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
pragma solidity 0.8.24;

// solhint-disable no-unused-import
import {CCIPLocalSimulator} from "@chainlink/local/src/ccip/CCIPLocalSimulator.sol";
7 changes: 5 additions & 2 deletions public/samples/CCIP/TokenTransferor.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
pragma solidity 0.8.24;

import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
@@ -235,7 +235,10 @@ contract TokenTransferor is OwnerIsCreator {
tokenAmounts: tokenAmounts, // The amount and type of token being transferred
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit to 0 as we are not sending any data
Client.EVMExtraArgsV1({gasLimit: 0})
Client.EVMExtraArgsV2({
gasLimit: 0, // Gas limit for the callback on the destination chain
allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages from the same sender
})
),
// Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
feeToken: _feeTokenAddress
2 changes: 1 addition & 1 deletion public/samples/CCIP/usdc/Receiver.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
pragma solidity 0.8.24;

import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
7 changes: 5 additions & 2 deletions public/samples/CCIP/usdc/Sender.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
pragma solidity 0.8.24;

import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
@@ -153,7 +153,10 @@ contract Sender is OwnerIsCreator {
tokenAmounts: tokenAmounts, // The amount and type of token being transferred
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit
Client.EVMExtraArgsV1({gasLimit: gasLimit})
Client.EVMExtraArgsV2({
gasLimit: gasLimit, // Gas limit for the callback on the destination chain
allowOutOfOrderExecution: true // Allows the message to be executed out of order relative to other messages from the same sender
})
),
// Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
feeToken: address(i_linkToken)
2 changes: 1 addition & 1 deletion public/samples/CCIP/usdc/Staker.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
pragma solidity 0.8.24;

import {ERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol";
2 changes: 1 addition & 1 deletion public/samples/DataStreams/StreamsUpkeep.sol
Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@ interface IFeeManager {
* @param quoteAddress The payment token address used for quoting fees and rewards.
* @return fee The fee assessed for verifying the report, with subscriber discounts applied where applicable.
* @return reward The reward allocated to the caller for successfully verifying the report.
* @return totalDiscount The total discount amount deducted from the fee for subscribers.
* @return totalDiscount The total discount amount deducted from the fee for subscribers
*/
function getFeeAndReward(
address subscriber,
11 changes: 11 additions & 0 deletions src/assets/product-logos/default-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 3 additions & 10 deletions src/assets/product-logos/general-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 3 additions & 17 deletions src/assets/product-logos/node-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 2 additions & 10 deletions src/assets/product-logos/quickstart-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/Address.tsx
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ const AddressComponent = ({

return (
<span className={`addressContainer ${urlClass || ""}`} id={urlId}>
<a title={address} className="addressLink" href={contractUrl}>
<a title={address} className="addressLink" href={contractUrl} target="_blank" rel="noopener noreferrer">
{endLength && address ? address.slice(0, endLength + 2) + "..." + address.slice(-endLength) : address}
</a>
<button
66 changes: 66 additions & 0 deletions src/components/AddressReact.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// This is a copy of the Preact Address component. This was required in the CCIP pages.
import { clsx } from "~/lib"

export type Props = {
contractUrl?: string
address?: string
endLength?: number
urlClass?: string
urlId?: string
eventName?: string
additionalInfo?: Record<string, string>
}

const AddressComponent = ({ contractUrl, address, endLength, urlClass, urlId }: Props) => {
address = address || contractUrl?.split("/").pop()

if (!address) return null

const handleClick = (e) => {
e.preventDefault()
if (address) navigator.clipboard.writeText(address)
}

return (
<span className={`addressContainer ${urlClass || ""}`} id={urlId}>
<a title={address} className="addressLink" href={contractUrl} target="_blank" rel="noopener noreferrer">
{endLength && address ? address.slice(0, endLength + 2) + "..." + address.slice(-endLength) : address}
</a>
<button
className={clsx("copyBtn", "copy-iconbutton")}
style={{ height: "16px", width: "16px", minWidth: "12px" }}
data-clipboard-text={address}
onClick={handleClick}
>
<img src="/assets/icons/copyIcon.svg" alt="Copy to clipboard" />
</button>

<style>{`
.addressLink {
padding: 1px 0px;
border-radius: var(--border-radius-10);
word-break: break-word;
}
.addressContainer {
display: inline-flex;
align-items: center;
gap: var(--space-1x);
word-break: break-word;
margin-top: 0;
}
.copyBtn {
background: none;
border: none;
}
.copyBtn:hover {
color: var(--color-text-link);
}
`}</style>
</span>
)
}

export default AddressComponent
7 changes: 4 additions & 3 deletions src/components/CCIP/Breadcrumb/Breadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import "./Breadcrumb.css"
import { Fragment } from "react"

interface BreadcrumbProps {
items: {
@@ -11,12 +12,12 @@ function Breadcrumb({ items }: BreadcrumbProps) {
return (
<div className="ccip-hero__breadcrumb">
{items.map((item, index) => (
<>
<a key={index} href={item.url} className="ccip-hero__breadcrumb__item">
<Fragment key={index}>
<a href={item.url} className="ccip-hero__breadcrumb__item">
{item.name}
</a>
{index < items.length - 1 && <img src="/assets/icons/breadcrumb-arrow.svg" alt="" />}
</>
</Fragment>
))}
</div>
)
32 changes: 19 additions & 13 deletions src/components/CCIP/Cards/NetworkCard.css
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
.network-card__container {
display: flex;
/* flex-direction: column;
align-items: flex-start; */
padding: 24px;
gap: 16px;
padding: var(--space-6x);
gap: var(--space-3x);
width: 100%;
background: var(--white);
border: 1px solid var(--gray-200);
border-radius: var(--space-1x);
}

background: #ffffff;
border: 1px solid #e4e8ed;
border-radius: 4px;
.network-card__container:hover {
background-color: var(--gray-50);
}

.network-card__container img {
width: 40px;
height: 40px;
width: var(--space-10x);
height: var(--space-10x);
margin-top: auto;
margin-bottom: auto;
}

.network-card__container h3 {
font-size: 16px;
font-weight: 500;
color: #252e42;
margin-bottom: 0;
font-size: var(--space-4x);
font-weight: var(--font-weight-medium);
line-height: var(--space-6x);
color: var(--gray-950);
margin-bottom: var(--space-1x);
}

.network-card__container p {
margin-bottom: 0;
font-size: var(--space-3x);
line-height: var(--space-5x);
color: var(--gray-500);
}
2 changes: 1 addition & 1 deletion src/components/CCIP/Cards/NetworkCard.tsx
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ function NetworkCard({ name, totalLanes, totalTokens, logo }: NetworkCardProps)
<div>
<h3>{name}</h3>
<p>
{totalLanes} lanes | {totalTokens} tokens
{totalLanes} {totalLanes > 1 ? "lanes" : "lane"} | {totalTokens} {totalTokens > 1 ? "tokens" : "token"}
</p>
</div>
</div>
38 changes: 28 additions & 10 deletions src/components/CCIP/Cards/TokenCard.css
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
.token-card__container {
display: flex;
width: 100%;
height: 110px;
min-width: 110px;
margin: 0 auto;
flex-direction: column;
align-items: center;
text-align: center;
padding: 24px;
gap: 16px;

padding: var(--space-4x);
gap: var(--space-3x);
background: #ffffff;
border: 1px solid #e4e8ed;
border-radius: 4px;
border: 1px solid var(--gray-200);
border-radius: var(--space-1x);
justify-content: center;
cursor: pointer;
}

.token-card__container:hover {
background-color: var(--gray-50);
}

.token-card__container img {
width: 40px;
height: 40px;
.token-card__container object,
.token-card__container object img {
width: var(--space-10x);
height: var(--space-10x);
border-radius: 50%;
}

.token-card__container h3 {
font-size: 16px;
font-size: var(--space-4x);
font-weight: 500;
color: #252e42;
color: var(--gray-950);
margin-bottom: 0;
}

.truncate {
@@ -28,3 +40,9 @@
text-overflow: ellipsis; /* Add ellipsis (…) when the text is truncated */
width: 200px; /* Adjust the width as needed */
}

@media (min-width: 992px) {
.token-card__container {
height: 124px;
}
}
47 changes: 34 additions & 13 deletions src/components/CCIP/Cards/TokenCard.tsx
Original file line number Diff line number Diff line change
@@ -4,23 +4,44 @@ import "./TokenCard.css"
interface TokenCardProps {
name: string
logo?: string
link?: string
onClick?: () => void
}

function TokenCard({ name, logo }: TokenCardProps) {
return (
<a href={`/ccip/token/${name}`}>
<div className="token-card__container">
<img
src={logo}
alt=""
onError={({ currentTarget }) => {
currentTarget.onerror = null // prevents looping
currentTarget.src = fallbackTokenIconUrl
}}
/>
function TokenCard({ name, logo, link, onClick }: TokenCardProps) {
if (link) {
return (
<a href={link}>
<div className="token-card__container">
{/* We cannot use the normal Image/onError syntax as a fallback as the element is server rendered
and the onerror does not seem to work correctly. Using Picutre will also not work. */}
<object data={logo} type="image/png">
<img src={fallbackTokenIconUrl} alt="" />
</object>
<h3>{name}</h3>
</div>
</a>
)
}

if (onClick) {
return (
<div className="token-card__container" onClick={onClick} role="button">
<object data={logo} type="image/png">
<img src={fallbackTokenIconUrl} alt="" />
</object>
<h3>{name}</h3>
</div>
</a>
)
}

return (
<div className="token-card__container">
<object data={logo} type="image/png">
<img src={fallbackTokenIconUrl} alt="" />
</object>
<h3>{name}</h3>
</div>
)
}

154 changes: 154 additions & 0 deletions src/components/CCIP/Chain/Chain.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
---
import CcipLayout from "~/layouts/CcipLayout.astro"
import { getEntry } from "astro:content"
import {
Environment,
getAllNetworkLanes,
getAllNetworks,
getSearchLanes,
getTokensOfChain,
Version,
} from "~/config/data/ccip"
import ChainHero from "~/components/CCIP/ChainHero/ChainHero"
import ChainTable from "~/components/CCIP/Tables/ChainTable"
import { getTokenIconUrl } from "~/features/utils"
import Drawer from "../Drawer/Drawer"
import ChainTokenGrid from "./ChainTokenGrid"
// TODO: Add type for network
const { environment, network } = Astro.props as { environment: Environment; network: any }
const entry = await getEntry("ccip", "index")
const { headings } = await entry.render()
const networks = getAllNetworks({ filter: environment })
const allTokens = getTokensOfChain({
chain: network?.chain || "",
filter: environment,
})
.map((token) => {
const logo = getTokenIconUrl(token) || ""
return {
name: token,
logo,
totalNetworks: networks.length, // Add totalNetworks property
}
})
.sort((a, b) => a.name.localeCompare(b.name))
const lanes = await getAllNetworkLanes({
environment: environment,
version: Version.V1_2_0,
chain: network?.chain || "",
site: Astro.url.origin || "",
})
const searchLanes = getSearchLanes({ environment })
---

<CcipLayout
frontmatter={{
title: `CCIP Supported Networks - ${network.name}`,
section: "ccip",
}}
{headings}
environment={environment}
>
<ChainHero
chains={networks}
tokens={allTokens}
network={network}
environment={environment}
lanes={searchLanes}
client:load
/>
<section class="layout">
<div>
<ChainTable
environment={environment}
lanes={lanes}
client:load
explorerUrl={network.explorerUrl}
sourceNetwork={{
name: network.name,
logo: network.logo,
key: network.chain,
}}
/>
</div>
<div>
<div class="ccip-heading">
<h2>Tokens <span>({allTokens.length})</span></h2>
<a class="button secondary" href="/ccip/concepts/cross-chain-tokens">Add my token</a>
</div>

<ChainTokenGrid tokens={allTokens} network={network} client:only="react" environment={environment} />
</div>
</section>
</CcipLayout>

<style scoped="false">
.layout {
--gutter: var(--space-10x);
--doc-padding: var(--space-6x);
margin: var(--space-8x) auto;
display: flex;
flex-direction: column;
gap: var(--gutter);
padding: var(--doc-padding);
}

.ccip-heading {
display: flex;
padding-bottom: var(--space-4x);
margin-bottom: var(--space-6x);
justify-content: space-between;
}

.layout h2 {
color: var(--gray-900);
font-size: 22px;
line-height: var(--space-10x);
margin-bottom: 0px;
}

.layout h2 span {
color: var(--gray-400);
font-weight: 600;
letter-spacing: 0.5px;
}

.networks__grid {
display: grid;
grid-template-columns: 1fr;
gap: var(--space-2x);
}

@media (min-width: 50em) {
.layout {
max-width: min(calc(1440px - 2 * var(--space-16x)), calc(100% - 2 * var(--space-16x)));
padding: 0;
}
}

@media (min-width: 992px) {
.layout {
display: grid;
--doc-padding: var(--space-10x);
padding-top: var(--doc-padding);
padding-bottom: var(--doc-padding);
grid-template-columns: 1fr 1fr;
gap: var(--space-24x);
}

.networks__grid {
grid-template-columns: 1fr 1fr;
gap: var(--space-6x);
min-height: 420px;
}

.ccip-heading {
border-bottom: 1px solid var(--gray-200);
}
}
</style>
13 changes: 13 additions & 0 deletions src/components/CCIP/Chain/ChainTokenGrid.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.tokens__grid {
display: grid;
grid-template-columns: 32% 32% 32%;
gap: var(--space-2x);
}

@media (min-width: 992px) {
.tokens__grid {
min-height: 420px;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: var(--space-4x);
}
}
97 changes: 97 additions & 0 deletions src/components/CCIP/Chain/ChainTokenGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Environment, getAllTokenLanes, getTokenData, Version } from "~/config/data/ccip"
import TokenCard from "../Cards/TokenCard"
import { drawerContentStore } from "../Drawer/drawerStore"
import TokenDrawer from "../Drawer/TokenDrawer"
import { directoryToSupportedChain, getChainIcon, getTitle } from "~/features/utils"
import { useState } from "react"
import "./ChainTokenGrid.css"
import SeeMore from "../SeeMore/SeeMore"

interface ChainTokenGridProps {
tokens: {
name: string
logo: string
totalNetworks: number
}[]
network: {
name: string
logo: string
tokenAddress: string
tokenPoolType: string
tokenPoolAddress: string
decimals: number
key: string
explorerUrl: string
}
environment: Environment
}

const BEFORE_SEE_MORE = 6 * 4 // Number of networks to show before the "See more" button, 7 rows x 4 items

function ChainTokenGrid({ tokens, network, environment }: ChainTokenGridProps) {
const [seeMore, setSeeMore] = useState(tokens.length <= BEFORE_SEE_MORE)
return (
<>
<div className="tokens__grid">
{tokens.slice(0, seeMore ? tokens.length : BEFORE_SEE_MORE).map((token) => {
const data = getTokenData({
environment,
version: Version.V1_2_0,
tokenSymbol: token.name || "",
})
return (
<TokenCard
name={token.name}
logo={token.logo}
key={token.name}
onClick={() => {
const selectedNetwork = Object.keys(data)
.map((key) => {
const directory = directoryToSupportedChain(key || "")
const title = getTitle(directory) || ""
const networkLogo = getChainIcon(directory) || ""
return {
name: title,
token: data[key].name || "",
key,
logo: networkLogo,
symbol: token,
tokenLogo: network.logo || "",
decimals: data[key].decimals || 0,
tokenAddress: data[key].tokenAddress || "",
tokenPoolType: data[key].poolType || "",
tokenPoolAddress: data[key].poolAddress || "",
explorerUrl: network.explorerUrl,
}
})
.find((n) => n.key === network.key)

if (selectedNetwork) {
drawerContentStore.set(() => (
<TokenDrawer
token={{
name: data[Object.keys(data)[0]]?.name || "",
logo: token.logo,
symbol: token.name,
}}
network={selectedNetwork}
destinationLanes={getAllTokenLanes({
environment,
version: Version.V1_2_0,
token: token.name || "",
})}
environment={environment}
/>
))
}
}}
/>
)
})}
</div>
{!seeMore && <SeeMore onClick={() => setSeeMore(!seeMore)} />}
</>
)
}

export default ChainTokenGrid
145 changes: 116 additions & 29 deletions src/components/CCIP/ChainHero/ChainHero.css
Original file line number Diff line number Diff line change
@@ -1,66 +1,119 @@
.ccip-hero {
.ccip-chain-hero {
background-color: var(--gray-100);
border-bottom: 1px solid var(--gray-200);
min-height: 241px;
}

.ccip-hero__grid {
width: 100vw;
}

.ccip-hero__heading {
.ccip-chain-hero__heading {
color: var(--gray-900);
font-size: 26px;
font-weight: 500;
display: flex;
align-items: center;
gap: var(--space-6x);
padding-top: var(--space-4x);
padding-bottom: var(--space-4x);
}

.ccip-chain-hero__heading h1 {
margin: 0;
font-size: 28px;
font-weight: 500;
}

.ccip-hero__heading img {
.ccip-chain-hero__heading img {
width: 80px;
height: 80px;
}

.ccip-hero__content {
--gutter: var(--space-2x);
--doc-padding: 2rem;
margin: var(--space-8x) auto;
.ccip-chain-hero__heading__images {
position: relative;
}

.ccip-chain-hero__heading__images__small {
width: var(--space-10x) !important;
height: var(--space-10x) !important;
border-radius: 50%;
top: 60%;
left: 60%;
position: absolute;
}

.ccip-chain-hero__content {
--gutter: var(--space-6x);
--doc-padding: var(--space-6x);
gap: var(--gutter);
padding: var(--doc-padding);
padding: var(--space-4x);
padding: var(--space-6x);
margin: 0 auto;
display: flex;
flex-direction: column;
}

.ccip-hero__top {
.ccip-chain-hero__token-logo {
border-radius: 50%;
}

.ccip-chain-hero__top {
display: flex;
flex-direction: column-reverse;
justify-content: space-between;
align-items: center;
gap: var(--space-16x);
align-items: start;
gap: var(--space-6x);
}

.ccip-hero__chainSearch {
width: 358px;
.ccip-chain-hero__chainSearch {
width: 100%;
}

.ccip-hero__details {
.ccip-chain-hero__details {
display: grid;
gap: var(--space-4x);
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
gap: var(--space-6x);
grid-template-columns: 1fr;
}

.ccip-hero__details__label {
.ccip-chain-hero__details__label {
/* Body/Body Semi S */
font-family: "Inter";
font-style: normal;
font-weight: 600;
font-size: 14px;
line-height: 22px;
/* identical to box height, or 157% */
padding-bottom: var(--space-2x);
color: var(--gray-500);
}

.ccip-chain-hero__feeTokens__list {
display: flex;
align-items: start;
flex-direction: column;
gap: var(--space-4x);
}

.ccip-chain-hero__feeTokens__item {
display: flex;

align-items: center;
gap: var(--space-2x);
color: var(--gray-900);
}

.ccip-chain-hero__feeTokens__native-gas-token {
font-style: normal;
font-weight: var(--font-weight-normal);
font-size: var(--space-4x);
line-height: var(--space-6x);
color: var(--gray-600);
}

.ccip-chain-hero__feeTokens__item a {
font-size: var(--space-4x) !important;
}

color: #6c7585;
.ccip-chain-hero__feeTokens__item__logo {
width: var(--space-5x);
height: var(--space-5x);
border-radius: 50%;
}

.ccip-hero__details__value {
.ccip-chain-hero__details__value {
/* Body/Body */
font-family: "Inter";
font-style: normal;
@@ -73,14 +126,48 @@
color: #212732;
}

.ccip-chain-hero__token-logo__symbol {
font-weight: 500;
font-size: 18px;
color: var(--gray-500);
}

@media (min-width: 50em) {
.ccip-hero__content {
.ccip-chain-hero__content {
max-width: 1440px;
}
}

@media (min-width: 992px) {
.ccip-hero__content {
.ccip-chain-hero__top {
flex-direction: row;
gap: var(--space-16x);
align-items: center;
}

.ccip-chain-hero__heading {
padding-top: 0;
padding-bottom: 0;
}

.ccip-chain-hero__chainSearch {
width: 358px;
}

.ccip-chain-hero__details {
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
gap: var(--space-4x);
}

.ccip-chain-hero__feeTokens__list {
align-items: center;
flex-direction: row;
gap: var(--space-20x);
}

.ccip-chain-hero__content {
--gutter: var(--space-10x);
grid-template-columns: 1fr 1fr;
padding: var(--space-10x) var(--space-16x);
}
}
297 changes: 254 additions & 43 deletions src/components/CCIP/ChainHero/ChainHero.tsx
Original file line number Diff line number Diff line change
@@ -1,88 +1,299 @@
import { Address } from "~/components"
import { Environment, getTokenData, LaneConfig, Version } from "~/config/data/ccip"
import Address from "~/components/AddressReact"
import Breadcrumb from "../Breadcrumb/Breadcrumb"
import Search from "../Search/Search"
import "./ChainHero.css"
import CopyValue from "../CopyValue/CopyValue"
import {
getExplorerAddressUrl,
getTokenIconUrl,
getNativeCurrency,
directoryToSupportedChain,
fallbackTokenIconUrl,
} from "~/features/utils"
import { Tooltip } from "~/features/common/Tooltip"

interface ChainHeroProps {
chains: {
name: string
totalLanes: number
totalTokens: number
logo: string
chain: string
}[]
tokens: {
name: string
totalNetworks: number
logo: string
}[]
network: {
lanes: {
sourceNetwork: {
name: string
logo: string
key: string
}
destinationNetwork: {
name: string
logo: string
key: string
explorerUrl: string
}
lane: LaneConfig
}[]
network?: {
name: string
logo: string
totalLanes: number
totalTokens: number
chain: string
tokenAdminRegistry?: string
registryModule?: string
router?: {
name: string
address: string
}
explorerUrl: string
routerExplorerUrl: string
chainSelector: string
feeTokens?: string[]
nativeToken?: {
name: string
symbol: string
logo: string
}
armProxy: {
address: string
version: string
}
}
token: {
token?: {
name: string
logo: string
symbol: string
}
environment: Environment
}

function ChainHero({ chains, tokens, network, token }: ChainHeroProps) {
return (
<section className="ccip-hero">
<img src="/assets/ccip.png" alt="" className="ccip-hero__grid" />
function ChainHero({ chains, tokens, network, token, environment, lanes }: ChainHeroProps) {
const feeTokensWithAddress = network?.feeTokens?.map((feeToken) => {
const logo = getTokenIconUrl(feeToken)
const token = getTokenData({
environment,
version: Version.V1_2_0,
tokenSymbol: feeToken,
})
const explorerUrl = network.explorerUrl
const address = getExplorerAddressUrl(explorerUrl)(token[network.chain].tokenAddress)

return {
logo,
token: feeToken,
address,
}
})

const nativeCurrency = ((network) => {
if (!network) return
const supportedNetwork = directoryToSupportedChain(network.chain)
return getNativeCurrency(supportedNetwork)
})(network)

<div className="ccip-hero__content layout">
<div className="ccip-hero__top">
const nativeTokenHasAddress = () => {
if (!network) return
// We making sure the Navive Currency is not already part of the FeeToken
return feeTokensWithAddress?.some((feeToken) => {
return feeToken.token.toLowerCase() === nativeCurrency?.symbol.toLowerCase()
})
}

return (
<section className="ccip-chain-hero">
<div className="ccip-chain-hero__content">
<div className="ccip-chain-hero__top">
<Breadcrumb
items={[
{
name: "Networks & Tokens",
url: "/ccip",
name: "CCIP Directory",
url: `/ccip/directory/${environment}`,
},
{
name: "Current",
url: `/ccip/${network.chain}`,
name: network?.name || token?.name || "Current",
url: network
? `/ccip/directory/${environment}/chain/${network.chain}`
: `/ccip/directory/${environment}/token/${token?.symbol}`,
},
]}
/>
<div className="ccip-hero__chainSearch">
<Search chains={chains} tokens={tokens} small />
<div className="ccip-chain-hero__chainSearch">
<Search chains={chains} tokens={tokens} small environment={environment} lanes={lanes} />
</div>
</div>

<h1 className="ccip-hero__heading">
<img src={network.logo} alt="" />
{network.name}
</h1>
<div className="ccip-hero__details">
<div className="ccip-hero__details__item">
<div className="ccip-hero__details__label">Router</div>
<div className="ccip-hero__details__value">
<Address
endLength={4}
contractUrl="https://etherscan.io/address/0x7a250d5630b4cf539739df2c5dacb4c659f2488d"
/>
<div className="ccip-chain-hero__heading">
<img
src={network?.logo || token?.logo}
alt=""
className={token?.logo ? "ccip-chain-hero__token-logo" : ""}
onError={({ currentTarget }) => {
currentTarget.onerror = null // prevents looping
currentTarget.src = fallbackTokenIconUrl
}}
/>
<h1>
{network?.name || token?.name} <span className="ccip-chain-hero__token-logo__symbol">{token?.symbol}</span>
</h1>
</div>
{network && (
<div className="ccip-chain-hero__details">
<div className="ccip-chain-hero__details__item">
<div className="ccip-chain-hero__details__label">Router</div>
<div className="ccip-chain-hero__details__value" data-clipboard-type="router">
<Address endLength={4} contractUrl={network.routerExplorerUrl} />
</div>
</div>
<div className="ccip-chain-hero__details__item">
<div className="ccip-chain-hero__details__label" data-clipboard-type="chain-selector">
Chain selector
<Tooltip
label=""
tip="CCIP Blockchain identifier"
labelStyle={{
marginRight: "5px",
}}
style={{
display: "inline-block",
verticalAlign: "middle",
marginBottom: "2px",
}}
/>
</div>
<div className="ccip-chain-hero__details__value">
{network.chainSelector ? <CopyValue value={network.chainSelector} /> : "n/a"}{" "}
</div>
</div>
<div className="ccip-chain-hero__details__item">
<div className="ccip-chain-hero__details__label">
RMN
<Tooltip
label=""
tip="The Risk Management contract maintains the list of Risk Management node addresses that are allowed to bless or curse. The contract also holds the quorum logic for blessing a committed Merkle Root and cursing CCIP on a destination blockchain."
labelStyle={{
marginRight: "5px",
}}
style={{
display: "inline-block",
verticalAlign: "middle",
marginBottom: "2px",
}}
/>
</div>
<div className="ccip-chain-hero__details__value" data-clipboard-type="rmn">
{network.armProxy ? (
<Address
endLength={4}
contractUrl={getExplorerAddressUrl(network.explorerUrl)(network.armProxy.address)}
/>
) : (
"n/a"
)}
</div>
</div>
<div className="ccip-chain-hero__details__item">
<div className="ccip-chain-hero__details__label">
Token admin registry
<Tooltip
label=""
tip="The TokenAdminRegistry contract is responsible for managing the configuration of token pools for all cross chain tokens."
labelStyle={{
marginRight: "5px",
}}
style={{
display: "inline-block",
verticalAlign: "middle",
marginBottom: "2px",
}}
/>
</div>
<div className="ccip-chain-hero__details__value" data-clipboard-type="token-registry">
{network.tokenAdminRegistry ? (
<Address
endLength={4}
contractUrl={getExplorerAddressUrl(network.explorerUrl)(network.tokenAdminRegistry)}
/>
) : (
"n/a"
)}
</div>
</div>
<div className="ccip-chain-hero__details__item">
<div className="ccip-chain-hero__details__label">
Registry module owner
<Tooltip
label=""
tip="The RegistryModuleOwnerCustom contract is responsible for registering the administrator of a token in the TokenAdminRegistry."
labelStyle={{
marginRight: "5px",
}}
style={{
display: "inline-block",
verticalAlign: "middle",
marginBottom: "2px",
}}
/>
</div>
<div className="ccip-chain-hero__details__value" data-clipboard-type="registry">
{network.registryModule ? (
<Address
endLength={4}
contractUrl={getExplorerAddressUrl(network.explorerUrl)(network.registryModule)}
/>
) : (
"n/a"
)}
</div>
</div>
</div>
<div className="ccip-hero__details__item">
<div className="ccip-hero__details__label">Chain selector</div>
<div className="ccip-hero__details__value">6433500567565415381</div>
</div>
<div className="ccip-hero__details__item">
<div className="ccip-hero__details__label">RMN</div>
<div className="ccip-hero__details__value">n/a</div>
</div>
<div className="ccip-hero__details__item">
<div className="ccip-hero__details__label">Token admin registry</div>
<div className="ccip-hero__details__value">n/a</div>
</div>
<div className="ccip-hero__details__item">
<div className="ccip-hero__details__label">Registry module owner</div>
<div className="ccip-hero__details__value">n/a</div>
)}

{feeTokensWithAddress && (
<div className="ccip-chain-hero__feeTokens">
<div className="ccip-chain-hero__details__label">Fee tokens</div>
<div className="ccip-chain-hero__feeTokens__list">
{feeTokensWithAddress.map(({ token, address, logo }, index) => {
return (
<div key={index} className="ccip-chain-hero__feeTokens__item" data-clipboard-type="fee-token">
<object
data={logo}
type="image/png"
width="20px"
height="20px"
className="ccip-chain-hero__feeTokens__item__logo"
>
<img src={fallbackTokenIconUrl} alt={token} width="20px" height="20px" />
</object>
<div>{token}</div>
<Address endLength={4} contractUrl={address} />
</div>
)
})}
{!nativeTokenHasAddress() && nativeCurrency && (
<div key={"native-token"} className="ccip-chain-hero__feeTokens__item">
<object
data={`${getTokenIconUrl(nativeCurrency.symbol)}`}
type="image/png"
width="20px"
height="20px"
className="ccip-chain-hero__feeTokens__item__logo"
>
<img src={fallbackTokenIconUrl} alt={`${nativeCurrency.symbol} icon`} width="20px" height="20px" />
</object>
<div>{nativeCurrency.symbol} </div>
<span className="ccip-chain-hero__feeTokens__native-gas-token">(native gas token)</span>
</div>
)}
</div>
</div>
</div>
)}
</div>
</section>
)
81 changes: 81 additions & 0 deletions src/components/CCIP/ChainHero/LaneDetailsHero.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
.lane-details-hero {
background-color: var(--gray-100);
padding: var(--space-6x);
border-bottom: 1px solid var(--gray-200);
}

.lane-details-hero h2 {
font-weight: var(--font-weight-medium);
padding: 0;
margin: 0;
font-size: var(--space-4x);
font-size: 22px;
line-height: 28px;
}

.lane-details-hero__networks {
display: flex;
flex-direction: column;
align-items: start;
gap: var(--space-6x);
font-style: normal;
font-weight: var(--font-weight-medium);
font-size: 28px;
margin: var(--space-10x) 0;
}

.lane-details-hero__networks svg {
transform: rotate(90deg);
margin-left: var(--space-4x);
}

.lane-details-hero__network {
display: flex;
align-items: center;
gap: var(--space-6x);
color: var(--gray-900);
}

.lane-details-hero__network img {
width: var(--space-12x);
height: var(--space-12x);
}

.lane-details-hero__details {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-6x);
}

.lane-details-hero__details__label {
font-style: normal;
font-weight: 600;
font-size: 14px;
line-height: 22px;

color: var(--gray-500);
}

.lane-details-hero__token-logo {
border-radius: 50%;
}

@media screen and (min-width: 768px) {
.lane-details-hero {
padding: var(--space-6x) var(--space-10x) var(--space-10x);
}

.lane-details-hero__details {
grid-template-columns: 1fr 2fr;
}

.lane-details-hero__networks {
flex-direction: row;
align-items: center;
}

.lane-details-hero__networks svg {
transform: rotate(0deg);
margin-left: 0;
}
}
136 changes: 136 additions & 0 deletions src/components/CCIP/ChainHero/LaneDetailsHero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { Tooltip } from "~/features/common/Tooltip"
import AddressComponent from "~/components/AddressReact"
import "./LaneDetailsHero.css"
import { getExplorerAddressUrl } from "~/features/utils"
import CopyValue from "../CopyValue/CopyValue"
import { LaneFilter } from "~/config/data/ccip"

interface LaneDetailsHeroProps {
sourceNetwork: {
logo: string
name: string
}
destinationNetwork: {
logo: string
name: string
}
onRamp: string
offRamp: string
destinationAddress: string
enforceOutOfOrder?: boolean
explorerUrl: string
rmnPermeable: boolean
inOutbound: LaneFilter
}

function LaneDetailsHero({
sourceNetwork,
destinationNetwork,
onRamp,
offRamp,
destinationAddress,
enforceOutOfOrder,
explorerUrl,
rmnPermeable,
inOutbound,
}: LaneDetailsHeroProps) {
let enforceOutOfOrderString = "N/A"
if (enforceOutOfOrder === true) {
enforceOutOfOrderString = "Required"
} else if (enforceOutOfOrder === false) {
enforceOutOfOrderString = "Optional"
}
return (
<div className="lane-details-hero">
<div className="lane-details-hero__networks">
<div className="lane-details-hero__network">
<img src={sourceNetwork.logo} alt={sourceNetwork.name} />
{sourceNetwork.name}
</div>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.666626 7.99673H14.6666M7.66663 15L14.6666 8L7.66663 1" stroke="var(--gray-900)" />
</svg>
<div className="lane-details-hero__network">
<img src={destinationNetwork.logo} alt={destinationNetwork.name} className="lane-details-hero__token-logo" />
{destinationNetwork.name}
</div>
</div>
<div className="lane-details-hero__details">
{inOutbound === LaneFilter.Inbound ? (
<>
<div className="lane-details-hero__details__label">OffRamp address</div>
<div data-clipboard-type="offramp">
<AddressComponent
address={offRamp}
endLength={6}
contractUrl={getExplorerAddressUrl(explorerUrl)(offRamp)}
/>
</div>
</>
) : (
<>
<div className="lane-details-hero__details__label">OnRamp address</div>
<div data-clipboard-type="onramp">
<AddressComponent
address={onRamp}
endLength={6}
contractUrl={getExplorerAddressUrl(explorerUrl)(onRamp)}
/>
</div>
</>
)}
<div className="lane-details-hero__details__label">Destination chain selector</div>
<div data-clipboard-type="destination-chain-selector">
{destinationAddress ? <CopyValue value={destinationAddress} /> : "n/a"}{" "}
</div>
<div className="lane-details-hero__details__label">RMN</div>
<div>
{rmnPermeable ? (
<a href="https://docs.chain.link/ccip/concepts#risk-management-network" target="_blank" rel="noreferrer">
<Tooltip
label="Coming soon"
tip="Risk Management Network (RMN) is NOT enabled for this lane at this time."
labelStyle={{
marginRight: "10px",
}}
style={{
display: "inline-flex",
}}
/>
</a>
) : (
<Tooltip
label="Enabled"
tip="This field shows the status of the Risk Management Network (RMN) for this lane."
labelStyle={{
marginRight: "10px",
}}
style={{
display: "inline-flex",
}}
/>
)}
</div>
{inOutbound === LaneFilter.Outbound && (
<>
<div className="lane-details-hero__details__label">Out of Order Execution</div>
<div data-clipboard-type="destination-chain-selector">
<Tooltip
label={enforceOutOfOrderString}
tip="Controls the execution order of your messages on the destination blockchain. Setting this to true allows messages to be executed in any order. Setting it to false ensures messages are executed in sequence, so a message will only be executed if the preceding one has been executed. On lanes where ‘Out of Order Execution’ is required, you must set this to true; otherwise, the transaction will revert."
labelStyle={{
marginRight: "10px",
}}
style={{
display: "inline-flex",
}}
/>
</div>
</>
)}
</div>
</div>
)
}

export default LaneDetailsHero
77 changes: 77 additions & 0 deletions src/components/CCIP/ChainHero/TokenDetailsHero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import Address from "~/components/AddressReact"
import { getExplorerAddressUrl, fallbackTokenIconUrl } from "~/features/utils"
import "./ChainHero.css"

interface TokenDetailsHeroProps {
network: {
name: string
logo: string
explorerUrl: string
}
token: {
name: string
symbol: string
logo: string
decimals: number
address: string
poolType: string
poolAddress: string
}
}

function TokenDetailsHero({ network, token }: TokenDetailsHeroProps) {
return (
<section className="ccip-chain-hero">
<div className="ccip-chain-hero__content">
<div className="ccip-chain-hero__heading">
<div className="ccip-chain-hero__heading__images">
<img src={network?.logo} alt="" />
<img
src={token?.logo}
alt=""
className="ccip-chain-hero__heading__images__small"
onError={(event) => {
;(event.target as HTMLImageElement).setAttribute("src", fallbackTokenIconUrl)
}}
/>
</div>

<h1>
{token?.name} <span className="ccip-chain-hero__token-logo__symbol">{token?.symbol}</span>
</h1>
</div>

<div className="ccip-chain-hero__details">
<div className="ccip-chain-hero__details__item">
<div className="ccip-chain-hero__details__label">Network</div>
<div className="ccip-chain-hero__details__value">{network?.name}</div>
</div>
<div className="ccip-chain-hero__details__item">
<div className="ccip-chain-hero__details__label">Decimals</div>
<div className="ccip-chain-hero__details__value">{token?.decimals}</div>
</div>
<div className="ccip-chain-hero__details__item">
<div className="ccip-chain-hero__details__label">Token address</div>
<div className="ccip-chain-hero__details__value" data-clipboard-type="token">
<Address endLength={4} contractUrl={getExplorerAddressUrl(network?.explorerUrl)(token?.address)} />
</div>
</div>
<div className="ccip-chain-hero__details__item">
<div className="ccip-chain-hero__details__label">Token pool type</div>
<div className="ccip-chain-hero__details__value">
{token?.poolType === "lockRelease" ? "Lock/Release" : "Burn/Mint"}
</div>
</div>
<div className="ccip-chain-hero__details__item">
<div className="ccip-chain-hero__details__label">Token pool address</div>
<div className="ccip-chain-hero__details__value" data-clipboard-type="token-pool">
<Address endLength={4} contractUrl={getExplorerAddressUrl(network?.explorerUrl)(token?.poolAddress)} />
</div>
</div>
</div>
</div>
</section>
)
}

export default TokenDetailsHero
56 changes: 56 additions & 0 deletions src/components/CCIP/CopyValue/CopyValue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { clsx } from "~/lib"

export type Props = {
value?: string
valueClass?: string
valueId?: string
}

const CopyValue = ({ value, valueClass, valueId }: Props) => {
if (!value) return null

const handleClick = (e) => {
e.preventDefault()
if (value) navigator.clipboard.writeText(value)
}

return (
<span className={`addressContainer ${valueClass || ""}`} id={valueId}>
<span className="valueCopy">{value}</span>
<button
className={clsx("copyBtn", "copy-iconbutton")}
style={{ height: "16px", width: "16px", minWidth: "12px" }}
data-clipboard-text={value}
onClick={handleClick}
>
<img src="/assets/icons/copyIcon.svg" alt="Copy to clipboard" />
</button>

<style>{`
.valueCopy {
border-radius: var(--border-radius-10);
word-break: break-word;
}
.addressContainer {
display: inline-flex;
align-items: center;
gap: var(--space-1x);
word-break: break-word;
margin-top: 0;
}
.copyBtn {
background: none;
border: none;
}
.copyBtn:hover {
color: var(--color-text-link);
}
`}</style>
</span>
)
}

export default CopyValue
90 changes: 90 additions & 0 deletions src/components/CCIP/Drawer/Drawer.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
.drawer {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 9999;
background-color: #1a2332e5;
backdrop-filter: blur(var(--space-1x)) opacity(0);
transition: backdrop-filter 0.5s ease-in-out;
}

.drawer__open.drawer {
backdrop-filter: blur(var(--space-1x)) opacity(0.9);
}

.drawer__close {
display: none;
}

.drawer__container {
position: fixed;
top: 0;
right: 0;
width: 100%;
height: 100vh;
background-color: white;
transform: translateX(100%);
transition: transform 0.5s ease-in-out;
}

.drawer__content {
overflow-y: auto;
height: 100%;
}

.drawer__open .drawer__container {
transform: translateX(0);
}

.drawer__closeMobile {
position: absolute;
top: var(--space-4x);
right: var(--space-4x);
color: black;
}

@media (min-width: 768px) {
.drawer__container {
width: 75%;
}

.drawer__closeMobile {
display: none;
}

.drawer__close {
position: fixed;
top: var(--space-4x);
right: calc(100% + var(--space-4x));
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-1x);
}

.drawer__close button {
width: var(--space-12x);
height: var(--space-12x);
background-color: var(--gray-900);
border-radius: var(--space-1x);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: background-color 0.3s ease-in-out;
}

.drawer__close:hover button {
background-color: var(--blue-700);
}

.drawer__close label {
color: var(--gray-500);
font-size: 9px;
font-family: Inter;
font-weight: 400;
line-height: 10.89px;
}
}
83 changes: 83 additions & 0 deletions src/components/CCIP/Drawer/Drawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { useStore } from "@nanostores/react"
import "./Drawer.css"
import { drawerContentStore } from "./drawerStore"
import { useRef, useEffect, useState } from "react"
import { clsx } from "~/lib"

function Drawer() {
const drawerRef = useRef<HTMLDivElement>(null)
const drawerContentRef = useRef<HTMLDivElement>(null)
const $drawerContent = useStore(drawerContentStore)
const [isOpened, setIsOpened] = useState(false)

// exit when press esc
useEffect(() => {
const handleKeyDown = (event) => {
if (event.key === "Escape") handleClose()
}

document.addEventListener("keydown", handleKeyDown)
if ($drawerContent) {
document.body.style.overflow = "hidden"
} else {
document.body.style.overflow = "auto"
}

if ($drawerContent) {
setIsOpened(true)
}
return () => {
document.removeEventListener("keydown", handleKeyDown)
document.body.style.overflow = "auto"
}
}, [$drawerContent])

if (!$drawerContent) return null

const handleClickOutside = (event) => {
if (drawerRef.current && drawerContentRef.current && !drawerContentRef.current.contains(event.target as Node)) {
handleClose()
}
}

const handleClose = () => {
setIsOpened(false)

// wait for animation to finish
setTimeout(() => {
drawerContentStore.set(null)
}, 500)
}

return (
<div
className={clsx("drawer", {
drawer__open: isOpened,
})}
ref={drawerRef}
onClick={handleClickOutside}
>
<div className="drawer__container" ref={drawerContentRef}>
<div className="drawer__close">
<button id="drawer-exit" onClick={handleClose}>
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.1667 1.33331L1.83337 14.6666M1.83337 1.33331L15.1667 14.6666" stroke="white" />
</svg>
</button>
<label htmlFor="drawer-exit">Esc</label>
</div>
<div className="drawer__content">
<button onClick={handleClose} className="drawer__closeMobile">
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.1667 1.33331L1.83337 14.6666M1.83337 1.33331L15.1667 14.6666" stroke="#000000" />
</svg>
</button>

<div>{typeof $drawerContent === "function" ? $drawerContent() : $drawerContent}</div>
</div>
</div>
</div>
)
}

export default Drawer
187 changes: 187 additions & 0 deletions src/components/CCIP/Drawer/LaneDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import Address from "~/components/AddressReact"
import "../Tables/Table.css"
import {
Environment,
getNetwork,
getTokenData,
LaneConfig,
LaneFilter,
Version,
displayCapacity,
} from "~/config/data/ccip"

import { useState } from "react"
import LaneDetailsHero from "../ChainHero/LaneDetailsHero"
import { getExplorerAddressUrl, getTokenIconUrl, fallbackTokenIconUrl } from "~/features/utils"
import TableSearchInput from "../Tables/TableSearchInput"
import RateTooltip from "../Tooltip/RateTooltip"
import { Tooltip } from "~/features/common/Tooltip"

function LaneDrawer({
lane,
sourceNetwork,
destinationNetwork,
environment,
inOutbound,
explorerUrl,
}: {
lane: LaneConfig
sourceNetwork: { name: string; logo: string; key: string }
destinationNetwork: { name: string; logo: string; key: string }
explorerUrl: string
environment: Environment
inOutbound: LaneFilter
}) {
const [search, setSearch] = useState("")
const destinationNetworkDetails = getNetwork({
filter: environment,
chain: destinationNetwork.key,
})

return (
<>
<h2 className="ccip-table__drawer-heading">Lane details</h2>
<LaneDetailsHero
sourceNetwork={{
logo: sourceNetwork.logo,
name: sourceNetwork.name,
}}
destinationNetwork={{
logo: destinationNetwork.logo,
name: destinationNetwork.name,
}}
onRamp={lane.onRamp.address}
offRamp={lane.offRamp.address}
enforceOutOfOrder={lane.onRamp.enforceOutOfOrder}
explorerUrl={explorerUrl || ""}
destinationAddress={destinationNetworkDetails?.chainSelector || ""}
rmnPermeable={lane.rmnPermeable}
inOutbound={inOutbound}
/>

<div className="ccip-table__drawer-container">
<div className="ccip-table__filters">
<div>
<div className="ccip-table__filters-title">
Tokens <span>({lane?.supportedTokens ? Object.keys(lane.supportedTokens).length : 0})</span>
</div>
</div>
<TableSearchInput search={search} setSearch={setSearch} />
</div>
<div className="ccip-table__wrapper">
<table className="ccip-table">
<thead>
<tr>
<th>Ticker</th>
<th>Token address (Source)</th>
<th>Decimals</th>
<th>Mechanism</th>
<th>
Rate limit capacity
<Tooltip
label=""
tip="Maximum amount per transaction"
labelStyle={{
marginRight: "5px",
}}
style={{
display: "inline-block",
verticalAlign: "middle",
marginBottom: "2px",
}}
/>
</th>
<th>
Rate limit refil rate
<Tooltip
label=""
tip="Rate at which available capacity is replenished"
labelStyle={{
marginRight: "5px",
}}
style={{
display: "inline-block",
verticalAlign: "middle",
marginBottom: "2px",
}}
/>
</th>
</tr>
</thead>
<tbody>
{lane.supportedTokens &&
Object.keys(lane.supportedTokens)
?.filter((token) => token.toLowerCase().includes(search.toLowerCase()))
.map((token, index) => {
const data = getTokenData({
environment,
version: Version.V1_2_0,
tokenSymbol: token || "",
})
if (!Object.keys(data).length) return null
const logo = getTokenIconUrl(token)
return (
<tr key={index}>
<td>
<a href={`/ccip/directory/${environment}/token/${token}`}>
<div className="ccip-table__network-name">
<img
src={logo}
alt={`${token} logo`}
className="ccip-table__logo"
onError={({ currentTarget }) => {
currentTarget.onerror = null // prevents looping
currentTarget.src = fallbackTokenIconUrl
}}
/>
{token}
</div>
</a>
</td>
<td data-clipboard-type="token">
<Address
address={data[sourceNetwork.key].tokenAddress}
endLength={6}
contractUrl={getExplorerAddressUrl(explorerUrl)(data[sourceNetwork.key].tokenAddress)}
/>
</td>
<td>{data[sourceNetwork.key].decimals}</td>

<td>{data[sourceNetwork.key].poolType === "lockRelease" ? "Lock/Release" : "Burn/Mint"}</td>
<td>
{lane.supportedTokens &&
displayCapacity(
data[sourceNetwork.key].decimals,
token,
lane.supportedTokens[token]?.rateLimiterConfig?.[
inOutbound === LaneFilter.Inbound ? "in" : "out"
]
)}
</td>
<td>
{lane.supportedTokens && (
<RateTooltip
destinationLane={lane.supportedTokens[token]}
inOutbound={inOutbound}
symbol={token}
decimals={data[sourceNetwork.key].decimals}
/>
)}
</td>
</tr>
)
})}
</tbody>
</table>
</div>
<div className="ccip-table__notFound">
{lane.supportedTokens &&
Object.keys(lane.supportedTokens)?.filter((lane) => lane.toLowerCase().includes(search.toLowerCase()))
.length === 0 && <>No tokens found</>}
</div>
</div>
</>
)
}

export default LaneDrawer
235 changes: 235 additions & 0 deletions src/components/CCIP/Drawer/TokenDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import "../Tables/Table.css"
import { drawerContentStore } from "../Drawer/drawerStore"
import TokenDetailsHero from "../ChainHero/TokenDetailsHero"
import {
Environment,
getLane,
getNetwork,
SupportedTokenConfig,
Version,
LaneFilter,
displayCapacity,
} from "~/config/data/ccip"
import { useState } from "react"
import { SupportedChain } from "~/config"
import LaneDrawer from "../Drawer/LaneDrawer"
import TableSearchInput from "../Tables/TableSearchInput"
import Tabs from "../Tables/Tabs"
import { Tooltip } from "~/features/common/Tooltip"
import RateTooltip from "../Tooltip/RateTooltip"

function TokenDrawer({
token,
network,
destinationLanes,
environment,
}: {
token: {
name: string
logo: string
symbol: string
}
network: {
name: string
logo: string
tokenAddress: string
tokenPoolType: string
tokenPoolAddress: string
decimals: number
key: string
explorerUrl: string
}
destinationLanes: {
[sourceChain: string]: SupportedTokenConfig
}
environment: Environment
}) {
const [search, setSearch] = useState("")
const [inOutbound, setInOutbound] = useState<LaneFilter>(LaneFilter.Outbound)

const laneRows = Object.keys(destinationLanes).map((lane) => {
const networkDetails = getNetwork({
filter: environment,
chain: lane,
})
const laneData = getLane({
sourceChain: network?.key as SupportedChain,
destinationChain: lane as SupportedChain,
environment,
version: Version.V1_2_0,
})
return { networkDetails, laneData, lane }
})
return (
<div>
<h2 className="ccip-table__drawer-heading">Token details</h2>
<TokenDetailsHero
token={{
name: token.name,
symbol: token.symbol,
logo: token.logo,
decimals: network.decimals,
address: network.tokenAddress,
poolType: network.tokenPoolType,
poolAddress: network.tokenPoolAddress,
}}
network={{
name: network.name,
logo: network.logo,
explorerUrl: network.explorerUrl,
}}
/>
<div className="ccip-table__drawer-container">
<div className="ccip-table__filters">
<div>
<Tabs
tabs={[
{
name: "Outbound lanes",
key: LaneFilter.Outbound,
},
{
name: "Inbound lanes",
key: LaneFilter.Inbound,
},
]}
onChange={(key) => setInOutbound(key as LaneFilter)}
/>
</div>
<TableSearchInput search={search} setSearch={setSearch} />
</div>
<div className="ccip-table__wrapper">
{" "}
<table className="ccip-table">
<thead>
<tr>
<th>{inOutbound === LaneFilter.Inbound ? "Source" : "Destination"} network</th>
<th>
Rate limit capacity
<Tooltip
label=""
tip="Maximum amount per transaction"
labelStyle={{
marginRight: "5px",
}}
style={{
display: "inline-block",
verticalAlign: "middle",
marginBottom: "2px",
}}
/>
</th>
<th>
Rate limit refil rate
<Tooltip
label=""
tip="Rate at which available capacity is replenished"
labelStyle={{
marginRight: "5px",
}}
style={{
display: "inline-block",
verticalAlign: "middle",
marginBottom: "2px",
}}
/>
</th>
<th>
Mechanism
<Tooltip
label=""
tip="Token pool mechanism: Lock & Mint, Burn & Mint, Lock & Unlock, Burn & Unlock."
labelStyle={{
marginRight: "5px",
}}
style={{
display: "inline-block",
verticalAlign: "middle",
marginBottom: "2px",
}}
/>
</th>
{/* <th>Status</th> */}
</tr>
</thead>
<tbody>
{laneRows
?.filter(
({ networkDetails }) =>
networkDetails && networkDetails.name.toLowerCase().includes(search.toLowerCase())
)
.map(({ networkDetails, laneData, lane }) => {
if (!laneData || !networkDetails) return null

return (
<tr key={networkDetails.name}>
<td>
<div
className="ccip-table__network-name"
role="button"
onClick={() => {
drawerContentStore.set(() => (
<LaneDrawer
environment={environment}
lane={laneData}
sourceNetwork={network}
destinationNetwork={{
name: networkDetails?.name || "",
logo: networkDetails?.logo || "",
key: lane,
}}
inOutbound={inOutbound}
explorerUrl={network.explorerUrl}
/>
))
}}
>
<img src={networkDetails?.logo} alt={networkDetails?.name} className="ccip-table__logo" />
{networkDetails?.name}
</div>
</td>
<td>
{displayCapacity(
network.decimals,
token.name,
destinationLanes[lane].rateLimiterConfig?.[inOutbound === LaneFilter.Inbound ? "in" : "out"]
)}
</td>
<td>
<RateTooltip
destinationLane={destinationLanes[lane]}
inOutbound={inOutbound}
symbol={token.symbol}
decimals={network.decimals}
/>
</td>
<td>{network.tokenPoolType === "lockRelease" ? "Lock/Release" : "Burn/Mint"}</td>
{/* <td>
<span className="ccip-table__status">
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M4.83329 8.49996L7.16663 10.8333L11.1666 5.83329M0.666626 8.49996C0.666626 10.4449 1.43925 12.3102 2.81451 13.6854C4.18978 15.0607 6.05504 15.8333 7.99996 15.8333C9.94489 15.8333 11.8102 15.0607 13.1854 13.6854C14.5607 12.3102 15.3333 10.4449 15.3333 8.49996C15.3333 6.55504 14.5607 4.68978 13.1854 3.31451C11.8102 1.93925 9.94489 1.16663 7.99996 1.16663C6.05504 1.16663 4.18978 1.93925 2.81451 3.31451C1.43925 4.68978 0.666626 6.55504 0.666626 8.49996Z"
stroke="#267E46"
/>
</svg>
Operational
</span>
</td> */}
</tr>
)
})}
</tbody>
</table>
</div>

<div className="ccip-table__notFound">
{laneRows?.filter(
({ networkDetails }) => networkDetails && networkDetails.name.toLowerCase().includes(search.toLowerCase())
).length === 0 && <>No lanes found</>}
</div>
</div>
</div>
)
}

export default TokenDrawer
3 changes: 3 additions & 0 deletions src/components/CCIP/Drawer/drawerStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { atom } from "nanostores"

export const drawerContentStore = atom<(() => JSX.Element) | null>(null)
5 changes: 1 addition & 4 deletions src/components/CCIP/Hero/Hero.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
.ccip-hero {
background-color: var(--gray-100);
}

.ccip-hero__grid {
width: 100vw;
border-bottom: 1px solid var(--gray-200);
}

.ccip-hero__heading {
24 changes: 20 additions & 4 deletions src/components/CCIP/Hero/Hero.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Environment, LaneConfig } from "~/config/data/ccip"
import Search from "../Search/Search"
import "./Hero.css"

@@ -7,21 +8,36 @@ interface HeroProps {
totalLanes: number
totalTokens: number
logo: string
chain: string
}[]
tokens: {
name: string
totalNetworks: number
logo: string
}[]
lanes: {
sourceNetwork: {
name: string
logo: string
key: string
}
destinationNetwork: {
name: string
logo: string
key: string
explorerUrl: string
}
lane: LaneConfig
}[]
environment: Environment
}

function Hero({ chains, tokens }: HeroProps) {
function Hero({ chains, tokens, environment, lanes }: HeroProps) {
return (
<section className="ccip-hero">
<img src="/assets/ccip.png" alt="" className="ccip-hero__grid" />
<div className="ccip-hero__content">
<h1 className="ccip-hero__heading">Networks & Tokens</h1>
<Search chains={chains} tokens={tokens} />
<h1 className="ccip-hero__heading">CCIP Directory</h1>
<Search chains={chains} tokens={tokens} environment={environment} lanes={lanes} />
</div>
</section>
)
12 changes: 12 additions & 0 deletions src/components/CCIP/Landing/NetworkGrid.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.networks__grid {
display: grid;
grid-template-columns: 1fr;
gap: var(--space-2x);
}

@media (min-width: 992px) {
.networks__grid {
grid-template-columns: 1fr 1fr;
gap: var(--space-6x);
}
}
41 changes: 41 additions & 0 deletions src/components/CCIP/Landing/NetworkGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useState } from "react"
import NetworkCard from "../Cards/NetworkCard"
import SeeMore from "../SeeMore/SeeMore"
import "./NetworkGrid.css"

interface NetworkGridProps {
networks: {
name: string
totalLanes: number
totalTokens: number
logo: string
chain: string
}[]
environment: string
}

const BEFORE_SEE_MORE = 2 * 7 // Number of networks to show before the "See more" button, 2 rows x 8 items

function NetworkGrid({ networks, environment }: NetworkGridProps) {
const [seeMore, setSeeMore] = useState(networks.length <= BEFORE_SEE_MORE)
return (
<>
<div className="networks__grid">
{networks.slice(0, seeMore ? networks.length : BEFORE_SEE_MORE).map((chain) => (
<a href={`/ccip/directory/${environment}/chain/${chain.chain}`} key={chain.chain}>
<NetworkCard
name={chain.name}
totalLanes={chain.totalLanes}
totalTokens={chain.totalTokens}
logo={chain.logo}
key={chain.chain}
/>
</a>
))}
</div>
{!seeMore && <SeeMore onClick={() => setSeeMore(!seeMore)} />}
</>
)
}

export default NetworkGrid
111 changes: 111 additions & 0 deletions src/components/CCIP/Landing/ccip-landing.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
---
import CcipLayout from "~/layouts/CcipLayout.astro"
import { getEntry } from "astro:content"
import Hero from "~/components/CCIP/Hero/Hero"
import {
Environment,
getAllNetworks,
getAllSupportedTokens,
getChainsOfToken,
getSearchLanes,
Version,
} from "~/config/data/ccip"
import { getTokenIconUrl } from "~/features/utils"
import NetworkGrid from "./NetworkGrid"
import TokenGrid from "../TokenGrid/TokenGrid"
export type Props = {
environment: Environment
}
const { environment } = Astro.props as Props
const entry = await getEntry("ccip", "index")
const { headings } = await entry.render()
const networks = getAllNetworks({ filter: environment })
const supportedTokens = getAllSupportedTokens({
environment: environment,
version: Version.V1_2_0,
})
const tokens = Object.keys(supportedTokens).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
const allTokens = tokens.map((token) => {
const logo = getTokenIconUrl(token) || ""
return {
name: token,
logo,
totalNetworks: getChainsOfToken({ token, filter: environment }).length,
}
})
const searchLanes = getSearchLanes({ environment })
---

<CcipLayout frontmatter={entry.data} {headings} environment={environment}>
<Hero chains={networks} tokens={allTokens} environment={environment} client:load lanes={searchLanes} />
<section class="layout">
<div>
<div class="ccip-heading">
<h2>Networks {environment} <span>({networks.length})</span></h2>
</div>
<NetworkGrid networks={networks} environment={environment} client:load />
</div>
<div>
<div class="ccip-heading">
<h2>Tokens <span>({allTokens.length})</span></h2>
<a class="button secondary" href="/ccip/concepts/cross-chain-tokens">Add my token</a>
</div>
<TokenGrid tokens={allTokens} environment={environment} client:load />
</div>
</section>
</CcipLayout>

<style>
.layout {
--gutter: var(--space-10x);
--doc-padding: var(--space-6x);
margin: var(--space-8x) auto;
display: flex;
flex-direction: column;
gap: var(--gutter);
padding: var(--doc-padding);
}

.ccip-heading {
display: flex;
padding-bottom: var(--space-4x);
border-bottom: 1px solid var(--gray-200);
margin-bottom: var(--space-6x);
justify-content: space-between;
}

.layout h2 {
color: var(--gray-900);
font-size: 22px;
line-height: var(--space-10x);
margin-bottom: 0px;
}

.layout h2 span {
color: var(--gray-400);
font-weight: 600;
letter-spacing: 0.5px;
}

@media (min-width: 50em) {
.layout {
max-width: min(calc(1440px - 2 * var(--space-16x)), calc(100% - 2 * var(--space-16x)));
padding: 0;
}
}

@media (min-width: 992px) {
.layout {
--doc-padding: var(--space-10x);
display: grid;
padding-top: var(--doc-padding);
padding-bottom: var(--doc-padding);
grid-template-columns: 1fr 1fr;
gap: var(--space-24x);
}
}
</style>
112 changes: 103 additions & 9 deletions src/components/CCIP/Search/Search.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.ccip-hero__search {
background-color: white;
padding: var(--space-4x);
padding: 18px;
display: flex;
gap: var(--space-4x);
width: 100%;
@@ -10,12 +10,25 @@
position: relative;
}

.ccip-hero__search-overlay {
content: "";
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.7);
z-index: 3;
}

.ccip-hero__search.active {
border-color: var(--gray-400);
z-index: 20;
}

.ccip-hero__search.small {
padding: var(--space-2x);
.ccip-hero__search.open {
border-color: var(--gray-400);
z-index: 3;
}

.ccip-hero__search input {
@@ -24,6 +37,30 @@
outline: none;
}

.ccip-hero__search input::-webkit-search-cancel-button {
-webkit-appearance: none;
height: 1em;
width: 1em;
border-radius: 50em;
background: url(/assets/icons/close-small.svg) no-repeat 50% 50%;
background-size: contain;
opacity: 0;
pointer-events: none;
}
.ccip-hero__search input:focus::-webkit-search-cancel-button {
opacity: 1;
pointer-events: all;
}

.ccip-hero__search-results__lane-images {
display: flex;
align-items: center;
}

.ccip-hero__search-results__lane-images img:nth-child(2) {
margin-left: -6px;
}

.ccip-hero__search-results {
position: absolute;
top: 110%;
@@ -33,9 +70,16 @@
border: 1px solid var(--gray-400);
border-radius: var(--space-1x);
z-index: 1;
padding: var(--space-6x) var(--space-10x);
max-height: 400px;
overflow-y: auto;
font-style: normal;
font-weight: 600;
font-size: var(--space-4x);
color: #000000;
}

.ccip-hero__search-results a {
color: #000000;
}

.ccip-hero__search-results ul {
@@ -47,13 +91,30 @@
display: flex;
gap: var(--space-4x);
align-items: center;
padding: var(--space-4x) 0;
padding-top: var(--space-3x);
padding-bottom: var(--space-3x);
cursor: pointer;
padding-left: var(--space-6x);
padding-right: var(--space-6x);
}

.ccip-hero__search-results--small a {
padding-left: var(--space-6x) !important;
padding-right: var(--space-6x) !important;
}

.ccip-hero__search-results--small .ccip-hero__search-results__title {
padding-left: var(--space-6x) !important;
padding-right: var(--space-6x) !important;
}

.ccip-hero__search-results li:hover {
background-color: var(--gray-50);
}

.ccip-hero__search-results li img {
width: 40px;
height: 40px;
width: var(--space-6x);
height: var(--space-6x);
}

.ccip-hero__search-results li span {
@@ -62,9 +123,42 @@
color: var(--gray-500);
}

.ccip-hero__search-results__title {
.ccip-hero__search-results__title,
.ccip-hero__search-results__no-result {
display: block;
font-size: 18px;
line-height: 24px;
color: var(--gray-500);
margin-bottom: 1010px;
padding-left: var(--space-6x);
padding-right: var(--space-6x);
padding-top: var(--space-6x);
}

.ccip-hero__search-results__no-result {
padding-bottom: var(--space-6x);
}

@media (min-width: 992px) {
.ccip-hero__search.small {
padding: var(--space-2x);
}

.ccip-hero__search-results li a {
display: flex;
gap: var(--space-4x);
align-items: center;
padding-top: var(--space-3x);
padding-bottom: var(--space-3x);
cursor: pointer;
padding-left: var(--space-10x);
padding-right: var(--space-10x);
}

.ccip-hero__search-results__title,
.ccip-hero__search-results__no-result {
padding-left: var(--space-10x);
padding-right: var(--space-10x);
padding-top: var(--space-6x);
padding-bottom: var(--space-2x);
}
}
217 changes: 157 additions & 60 deletions src/components/CCIP/Search/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { useState, useEffect } from "react"
import { useState, useEffect, useRef } from "react"
import "./Search.css"
import { clsx } from "~/lib"
import { useClickOutside } from "~/hooks/useClickOutside"
import { Environment, LaneConfig, LaneFilter } from "~/config/data/ccip"
import { directoryToSupportedChain, getExplorer } from "~/features/utils"
import { drawerContentStore } from "../Drawer/drawerStore"
import LaneDrawer from "../Drawer/LaneDrawer"

interface SearchProps {
chains: {
@@ -15,86 +20,178 @@ interface SearchProps {
totalNetworks: number
logo: string
}[]
lanes: {
sourceNetwork: {
name: string
logo: string
key: string
}
destinationNetwork: {
name: string
logo: string
key: string
explorerUrl: string
}
lane: LaneConfig
}[]
small?: boolean
environment: Environment
}

function Search({ chains, tokens, small }: SearchProps) {
function Search({ chains, tokens, small, environment, lanes }: SearchProps) {
const [search, setSearch] = useState("")
const [openSearchMenu, setOpenSearchMenu] = useState(false)
const [isActive, setIsActive] = useState(false)
const [networksResults, setNetworksResults] = useState<SearchProps["chains"]>([])
const [tokensResults, setTokensResults] = useState<SearchProps["tokens"]>([])
const [lanesResults, setLanesResults] = useState<SearchProps["lanes"]>([])
const searchRef = useRef<HTMLInputElement>(null)

useEffect(() => {
if (search) {
const networks = chains.filter((chain) => chain.name.toLowerCase().includes(search.toLowerCase()))
const tokensList = tokens.filter((token) => token.name.toLowerCase().includes(search.toLowerCase()))

const lanesList = lanes.filter(
(lane) =>
(lane.sourceNetwork.name.toLowerCase().includes(search.toLowerCase()) ||
lane.destinationNetwork.name.toLowerCase().includes(search.toLowerCase())) &&
(lane.lane.supportedTokens ? Object.keys(lane.lane.supportedTokens).length : 0) > 0
)
setNetworksResults(networks)
setTokensResults(tokensList)
setLanesResults(lanesList)
setOpenSearchMenu(true)
} else {
setNetworksResults([])
setTokensResults([])
setLanesResults([])
setOpenSearchMenu(false)
}
}, [search, chains, tokens])

useClickOutside(searchRef, () => setOpenSearchMenu(false))

const generateExplorerUrl = (lane) => {
const directory = directoryToSupportedChain(lane.sourceNetwork.key)
return getExplorer(directory) || ""
}
return (
<div
className={clsx("ccip-hero__search", {
active: isActive,
small: small || false,
})}
>
<img src="/assets/icons/search.svg" alt="" />
<input
type="search"
placeholder="Network/Token/Lane"
value={search}
onChange={(e) => setSearch(e.target.value)}
onFocus={() => setIsActive(true)}
onBlur={() => setIsActive(false)}
/>
{search && (
<div className="ccip-hero__search-results">
{networksResults.length === 0 && tokensResults.length === 0 && (
<span className="ccip-hero__search-results__title">No results found</span>
)}
{networksResults.length > 0 && (
<>
<span className="ccip-hero__search-results__title">Networks</span>
<ul aria-label="Networks">
{networksResults.map((network) => (
<li key={network.name}>
<a href={`/ccip/chain/${network.chain}`}>
<img src={network.logo} alt="" />
{network.name}
<span>
{network.totalLanes} lanes | {network.totalTokens} tokens
</span>
</a>
</li>
))}
</ul>
</>
)}
{tokensResults.length > 0 && (
<>
<span className="ccip-hero__search-results__title">Tokens</span>
<ul aria-label="Networks">
{tokensResults.map((token) => (
<li key={token.name}>
<a href="#">
<img src={token.logo} alt="" />
{token.name}
<span>{token.totalNetworks} networks</span>
</a>
</li>
))}
</ul>
</>
)}
</div>
)}
</div>
<>
{openSearchMenu && <div className="ccip-hero__search-overlay"></div>}
<div
className={clsx("ccip-hero__search", {
active: isActive,
small: small || false,
open: openSearchMenu,
})}
ref={searchRef}
>
<img src="/assets/icons/search.svg" alt="" />
<input
type="search"
placeholder="Network/Token/Lane"
value={search}
onChange={(e) => setSearch(e.target.value)}
onFocus={() => setIsActive(true)}
onBlur={() => setIsActive(false)}
/>
{openSearchMenu && (
<div
className={clsx("ccip-hero__search-results", {
"ccip-hero__search-results--small": small || false,
})}
>
{networksResults.length === 0 && tokensResults.length === 0 && (
<span className="ccip-hero__search-results__no-result">No results found</span>
)}
{networksResults.length > 0 && (
<>
<span className="ccip-hero__search-results__title">Networks</span>
<ul aria-label="Networks">
{networksResults.map((network) => (
<li key={network.name}>
<a href={`/ccip/directory/${environment}/chain/${network.chain}`}>
<img src={network.logo} alt="" />
{network.name}
{!small && (
<span>
{network.totalLanes} {network.totalLanes > 1 ? "lanes" : "lane"} | {network.totalTokens}{" "}
{network.totalTokens > 1 ? "tokens" : "token"}
</span>
)}
</a>
</li>
))}
</ul>
</>
)}
{tokensResults.length > 0 && (
<>
<span className="ccip-hero__search-results__title">Tokens</span>
<ul aria-label="Networks">
{tokensResults.map((token) => (
<li key={token.name}>
<a href={`/ccip/directory/${environment}/token/${token.name}`}>
<img src={token.logo} alt="" />
{token.name}
{!small && (
<span>
{token.totalNetworks} {token.totalNetworks > 1 ? "networks" : "network"}
</span>
)}
</a>
</li>
))}
</ul>
</>
)}

{lanesResults.length > 0 && (
<>
<span className="ccip-hero__search-results__title">Lanes</span>
<ul aria-label="Networks">
{lanesResults.map((lane) => (
<li key={lane.sourceNetwork.name + lane.destinationNetwork.key}>
<a
role="button"
onClick={() =>
drawerContentStore.set(() => (
<LaneDrawer
environment={environment}
lane={lane.lane}
sourceNetwork={lane.sourceNetwork}
destinationNetwork={{
...lane.destinationNetwork,
}}
inOutbound={LaneFilter.Outbound}
explorerUrl={generateExplorerUrl(lane)}
/>
))
}
>
<div className="ccip-hero__search-results__lane-images">
<img src={lane.sourceNetwork.logo} alt="" />
<img src={lane.destinationNetwork.logo} alt="" />
</div>
{lane.sourceNetwork.name} {">"} {lane.destinationNetwork.name}
{!small && (
<span>
{lane?.lane?.supportedTokens ? Object.keys(lane.lane.supportedTokens).length : 0}{" "}
{lane?.lane?.supportedTokens && Object.keys(lane.lane.supportedTokens).length > 1
? "tokens"
: "token"}
</span>
)}
</a>
</li>
))}
</ul>
</>
)}
</div>
)}
</div>
</>
)
}

12 changes: 12 additions & 0 deletions src/components/CCIP/SeeMore/SeeMore.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.seeMore {
height: 22px;
font-size: 14px;
line-height: 22px;
color: #2e7bff;
margin-top: var(--space-6x);
}
.seeMore__container {
display: flex;
justify-content: center;
align-items: center;
}
16 changes: 16 additions & 0 deletions src/components/CCIP/SeeMore/SeeMore.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import "./SeeMore.css"
interface SeeMoreProps {
onClick?: () => void
}

function SeeMore({ onClick }: SeeMoreProps) {
return (
<div className="seeMore__container">
<button className="seeMore" onClick={onClick}>
See more
</button>
</div>
)
}

export default SeeMore
199 changes: 158 additions & 41 deletions src/components/CCIP/Tables/ChainTable.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,170 @@
import Address from "~/components/AddressReact"
import "./Table.css"
import Tabs from "./Tabs"
import TableSearchInput from "./TableSearchInput"
import { useEffect, useState } from "react"
import { getExplorerAddressUrl } from "~/features/utils"
import { drawerContentStore } from "../Drawer/drawerStore"
import LaneDrawer from "../Drawer/LaneDrawer"
import { Environment, Version } from "~/config/data/ccip/types"
import { getLane, LaneFilter } from "~/config/data/ccip"
import { SupportedChain } from "~/config"
import { clsx } from "~/lib"
import SeeMore from "../SeeMore/SeeMore"
import { getOperationalState } from "~/config/data/ccip/data"

interface TableProps {
networks: {
environment: Environment
sourceNetwork: { name: string; logo: string; key: string }
lanes: {
name: string
key: string
logo: string
onramp?: string
status: string
totalLanes: number
totalTokens: number
chain: string
onRamp?: {
address: string
version: string
}
offRamp?: {
address: string
version: string
}
key: string
directory: SupportedChain
}[]
explorerUrl: string
}

function ChainTable({ networks }: TableProps) {
const BEFORE_SEE_MORE = 12 // Number of networks to show before the "See more" button, 7 rows

function ChainTable({ lanes, explorerUrl, sourceNetwork, environment }: TableProps) {
const [inOutbound, setInOutbound] = useState<LaneFilter>(LaneFilter.Outbound)
const [search, setSearch] = useState("")
const [seeMore, setSeeMore] = useState(lanes.length <= BEFORE_SEE_MORE)
const [statuses, setStatuses] = useState<Record<string, string>>({})
const [loadingStatuses, setLoadingStatuses] = useState<boolean>(true)

useEffect(() => {
if (search.length > 0) {
setSeeMore(true)
}
}, [search])

useEffect(() => {
const fetchOperationalState = async (network) => {
if (network) {
const result = await getOperationalState(network)
setStatuses(result)
setLoadingStatuses(false)
}
}
fetchOperationalState(sourceNetwork.key)
}, [sourceNetwork])

return (
<table className="ccip-table">
<thead>
<tr>
<th>Destination network</th>
<th>OnRamp address</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{networks?.map((network, index) => (
<tr key={index}>
<td>
<div className="ccip-table__network-name">
<img src={network.logo} alt={network.name} className="ccip-table__logo" />
{network.name}
</div>
</td>
<td>{network.onramp}0x1234567890</td>
<td>
<span className="ccip-table__status">
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M4.83329 8.49996L7.16663 10.8333L11.1666 5.83329M0.666626 8.49996C0.666626 10.4449 1.43925 12.3102 2.81451 13.6854C4.18978 15.0607 6.05504 15.8333 7.99996 15.8333C9.94489 15.8333 11.8102 15.0607 13.1854 13.6854C14.5607 12.3102 15.3333 10.4449 15.3333 8.49996C15.3333 6.55504 14.5607 4.68978 13.1854 3.31451C11.8102 1.93925 9.94489 1.16663 7.99996 1.16663C6.05504 1.16663 4.18978 1.93925 2.81451 3.31451C1.43925 4.68978 0.666626 6.55504 0.666626 8.49996Z"
stroke="#267E46"
/>
</svg>
Operational
</span>
</td>
</tr>
))}
</tbody>
</table>
<>
<div className="ccip-table__filters">
<Tabs
tabs={[
{
name: "Outbound lanes",
key: LaneFilter.Outbound,
},
{
name: "Inbound lanes",
key: LaneFilter.Inbound,
},
]}
onChange={(key) => setInOutbound(key as LaneFilter)}
/>
<TableSearchInput search={search} setSearch={setSearch} />
</div>
<div className="ccip-table__wrapper">
<table className="ccip-table">
<thead>
<tr>
<th>{inOutbound === LaneFilter.Outbound ? "Destination" : "Source"} network</th>
<th>{inOutbound === LaneFilter.Outbound ? "OnRamp" : "OffRamp"} address</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{lanes
?.filter((network) => network.name.toLowerCase().includes(search.toLowerCase()))
.slice(0, seeMore ? lanes.length : BEFORE_SEE_MORE)
.map((network, index) => (
<tr key={index}>
<td>
<div
className="ccip-table__network-name"
role="button"
onClick={() => {
const laneData = getLane({
sourceChain: sourceNetwork.key as SupportedChain,
destinationChain: network.key as SupportedChain,
environment,
version: Version.V1_2_0,
})

drawerContentStore.set(() => (
<LaneDrawer
environment={environment}
lane={laneData}
sourceNetwork={sourceNetwork}
destinationNetwork={{
name: network?.name || "",
logo: network?.logo || "",
key: network.key,
}}
inOutbound={inOutbound}
explorerUrl={explorerUrl}
/>
))
}}
>
<img src={network.logo} alt={network.name} className="ccip-table__logo" />
{network.name}
</div>
</td>
<td data-clipboard-type={inOutbound === LaneFilter.Outbound ? "onramp" : "offramp"}>
<Address
address={inOutbound === LaneFilter.Outbound ? network.onRamp?.address : network.offRamp?.address}
endLength={4}
contractUrl={getExplorerAddressUrl(explorerUrl)(
(inOutbound === LaneFilter.Outbound ? network.onRamp?.address : network.offRamp?.address) || ""
)}
/>
</td>
<td>
{loadingStatuses ? (
"Loading..."
) : (
<span
className={clsx(
"ccip-table__status",
`ccip-table__status-${statuses[network.key]?.toLocaleLowerCase() || "none"}`
)}
>
{statuses[network.key]?.toLocaleLowerCase() && (
<img
src={`/assets/icons/ccip-${statuses[network.key]?.toLocaleLowerCase()}.svg`}
alt="Cursed"
/>
)}
{statuses[network.key]?.toLocaleLowerCase() || "Status unavailable"}
</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
{!seeMore && <SeeMore onClick={() => setSeeMore(!seeMore)} />}
<div className="ccip-table__notFound">
{lanes.filter((network) => network.name.toLowerCase().includes(search.toLowerCase())).length === 0 && (
<>No lanes found</>
)}
</div>
</>
)
}

Loading