From a2640a460929efd82cfc778386456eee4419f792 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 30 Oct 2024 16:25:12 -0700 Subject: [PATCH 01/19] WIP --- src/eip1193_index.html | 173 +++++++++++++++++++++ src/multichain_index.html | 318 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 491 insertions(+) create mode 100644 src/eip1193_index.html create mode 100644 src/multichain_index.html diff --git a/src/eip1193_index.html b/src/eip1193_index.html new file mode 100644 index 00000000..802ed47b --- /dev/null +++ b/src/eip1193_index.html @@ -0,0 +1,173 @@ + + + + + + Ethereum DApp + + +

Ethereum EIP-1193 API DApp

+ +
+
Connected Accounts: Not available
+

+ + +

+ +

+
Current Chain: Not available
+
Current Block Head: Not available
+ + + + diff --git a/src/multichain_index.html b/src/multichain_index.html new file mode 100644 index 00000000..f9a07191 --- /dev/null +++ b/src/multichain_index.html @@ -0,0 +1,318 @@ + + + + + + Ethereum DApp + + +

Ethereum Multichain API DApp

+
+ +
+
Connected Extension: Not available
+

+ +
+
Connected Accounts: Not available
+

+ + +

+ +

+ +

+
+ + + + From b0dad25efd1dd3a19b179fa40d4b3db524cfff51 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 30 Oct 2024 17:22:29 -0700 Subject: [PATCH 02/19] Add 1193 block head red color --- src/eip1193_index.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/eip1193_index.html b/src/eip1193_index.html index 802ed47b..dc80604c 100644 --- a/src/eip1193_index.html +++ b/src/eip1193_index.html @@ -6,7 +6,7 @@ Ethereum DApp -

Ethereum EIP-1193 API DApp

+

Ethereum EIP-1193 API Dapp


Connected Accounts: Not available
@@ -107,7 +107,12 @@

Ethereum EIP-1193 API DApp

window.ethereum.on('message', (message) => { if (message.type === 'eth_subscription' && message.data.subscription === subscriptionId) { const blockHead = message.data.result; - document.getElementById('blockHead').innerText = `Current Block Head: ${parseInt(blockHead.number, 16)}`; + const blockHeadDisplay = document.getElementById('blockHead') + blockHeadDisplay.innerText = `Current Block Head: ${parseInt(blockHead.number, 16)}`; + blockHeadDisplay.style.color = "red"; + setTimeout(() => { + blockHeadDisplay.style.color = "black"; + }, 1500) } }); }).catch(error => { From a9be168045338e70fea934e6b36f9999826c1fe2 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 30 Oct 2024 17:22:45 -0700 Subject: [PATCH 03/19] Add multichain connection request textarea --- src/multichain_index.html | 84 ++++++++++++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/src/multichain_index.html b/src/multichain_index.html index f9a07191..4ea631d6 100644 --- a/src/multichain_index.html +++ b/src/multichain_index.html @@ -6,14 +6,20 @@ Ethereum DApp -

Ethereum Multichain API DApp

+

Ethereum Multichain API Dapp



Connected Extension: Not available


- + + + +
+
+ +

Connected Accounts: Not available


@@ -136,6 +142,7 @@

Ethereum Multichain API DApp

// Initial setup checkWalletConnection(); + connectRequestFull(); } catch (error) { console.error(error); alert('Failed to connect to extension!'); @@ -177,21 +184,65 @@

Ethereum Multichain API DApp

}) } + + function connectRequestFull() { + const params = { + optionalScopes: { + "eip155": { + "references": ["1", "59141", "11155111", "59144"], + "methods": ["personal_sign", "eth_signTypedData_v4", "wallet_watchAsset", "eth_sendTransaction", "eth_subscribe", "eth_unsubscribe"], + "notifications": ["eth_subscription"] + }, + } + } + + const textarea = document.getElementById('connectRequestTextarea') + textarea.value = JSON.stringify(params, null, 2) + } + + function connectRequestOnlySend() { + const params = { + optionalScopes: { + "eip155": { + "references": ["1", "59141", "11155111", "59144"], + "methods": ["eth_sendTransaction"], + "notifications": [] + }, + } + } + + const textarea = document.getElementById('connectRequestTextarea') + textarea.value = JSON.stringify(params, null, 2) + } + + function connectRequestOnlySubscribe() { + const params = { + optionalScopes: { + "eip155": { + "references": ["1", "59141", "11155111", "59144"], + "methods": ["eth_subscribe"], + "notifications": ["eth_subscription"] + }, + } + } + + const textarea = document.getElementById('connectRequestTextarea') + textarea.value = JSON.stringify(params, null, 2) + } + // Function to connect wallet async function connectWallet() { + if (!extensionPort) { + alert('Connect to extension first.') + return + } try { + const textarea = document.getElementById('connectRequestTextarea') + const params = JSON.parse(textarea.value) + const {sessionScopes} = await extensionPortRequest({ method: 'wallet_createSession', - params: { - optionalScopes: { - "eip155": { - "references": ["1", "59141", "11155111", "59144"], - // reduce methods here - "methods": ["personal_sign", "eth_signTypedData_v4", "wallet_watchAsset", "eth_sendTransaction", "eth_decrypt", "eth_getEncryptionPublicKey", "web3_clientVersion", "eth_subscribe", "eth_unsubscribe", "eth_blockNumber", "eth_call", "eth_chainId", "eth_estimateGas", "eth_feeHistory", "eth_gasPrice", "eth_getBalance", "eth_getBlockByHash", "eth_getBlockByNumber", "eth_getBlockTransactionCountByHash", "eth_getBlockTransactionCountByNumber", "eth_getCode", "eth_getFilterChanges", "eth_getFilterLogs", "eth_getLogs", "eth_getProof", "eth_getStorageAt", "eth_getTransactionByBlockHashAndIndex", "eth_getTransactionByBlockNumberAndIndex", "eth_getTransactionByHash", "eth_getTransactionCount", "eth_getTransactionReceipt", "eth_getUncleCountByBlockHash", "eth_getUncleCountByBlockNumber", "eth_newBlockFilter", "eth_newFilter", "eth_newPendingTransactionFilter", "eth_sendRawTransaction", "eth_syncing", "eth_uninstallFilter"], - "notifications": ["eth_subscription"] - }, - } - } + params }) await onNewSessionScopes(sessionScopes) @@ -301,9 +352,6 @@

Ethereum Multichain API DApp

} } - // Event listener for connect wallet button - document.getElementById('connectButton').addEventListener('click', connectWallet); - // Event listener for connect extension button document.getElementById('connectExtensionButton').addEventListener('click', connectExtension); @@ -313,6 +361,12 @@

Ethereum Multichain API DApp

// Event listener for subscribe button document.getElementById('subscribeButton').addEventListener('click', subscribeToBlockHeaders); + // Event listener for connect wallet buttons + document.getElementById('connectButton').addEventListener('click', connectWallet); + document.getElementById('connectRequestFullButton').addEventListener('click', connectRequestFull); + document.getElementById('connectRequestOnlySendButton').addEventListener('click', connectRequestOnlySend); + document.getElementById('connectRequestOnlySubscribeButton').addEventListener('click', connectRequestOnlySubscribe); + From 2571c6299b62b542ad79764866a1a0353b446fe5 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 30 Oct 2024 18:20:29 -0700 Subject: [PATCH 04/19] prettify --- src/eip1193_index.html | 340 +++++++++--------- src/multichain_index.html | 729 +++++++++++++++++++------------------- 2 files changed, 542 insertions(+), 527 deletions(-) diff --git a/src/eip1193_index.html b/src/eip1193_index.html index dc80604c..f5adfcff 100644 --- a/src/eip1193_index.html +++ b/src/eip1193_index.html @@ -1,178 +1,178 @@ - - - Ethereum DApp + + + Ethereum EIP-1193 API Dapp -

Ethereum EIP-1193 API Dapp

- -
-
Connected Accounts: Not available
-

- - -

- -

-
Current Chain: Not available
-
Current Block Head: Not available
- - + }); + }).catch(error => { + console.error('Failed to subscribe to new block headers:', error); + }); + } + + async function checkWalletConnection() { + try { + accounts = await window.ethereum.request({ + method: 'eth_accounts' + }); + if (accounts.length > 0) { + updateConnectedAccountsDisplay(); + await updateCurrentChainDisplay(); + await subscribeToBlockHeaders(); + } + } catch (error) { + console.error('Failed to check wallet connection:', error); + } + } + + + async function updateCurrentChainDisplay() { + const currentChainId = await getCurrentChainId(); + document.getElementById('currentChain').innerText = `Current Chain: ${currentChainId}`; + } + + function updateConnectedAccountsDisplay() { + document.getElementById('connectedAccounts').innerText = `Connected Accounts: ${accounts.join(', ')}`; + } + + // Events + window.ethereum.on('chainChanged', async (chainId) => { + const selectedChainId = document.getElementById('chainSelect').value; + if (chainId !== selectedChainId) { + alert(`Wallet has changed the current chain to ${chainId}. Will try to change it back to ${selectedChainId} now.`); + await switchChain(selectedChainId); + } + document.getElementById('blockHead').innerText = 'Current Block Head: Not available'; + await updateCurrentChainDisplay(); + await subscribeToBlockHeaders(); + }); + + window.ethereum.on('accountsChanged', async (newAccounts) => { + accounts = newAccounts + updateConnectedAccountsDisplay(); + }); + + // DOM + document.getElementById('connectButton').addEventListener('click', connectWallet); + document.getElementById('chainSelect').addEventListener('change', async (event) => { + const chainId = event.target.value; + await switchChain(chainId); + await subscribeToBlockHeaders(); + await updateCurrentChainDisplay(); + }); + document.getElementById('sendTxButton').addEventListener('click', sendTransaction); + + // Initial setup + checkWalletConnection(); + diff --git a/src/multichain_index.html b/src/multichain_index.html index 4ea631d6..1a4acbc7 100644 --- a/src/multichain_index.html +++ b/src/multichain_index.html @@ -1,372 +1,387 @@ - - - Ethereum DApp + + + Ethereum Multichain API Dapp -

Ethereum Multichain API Dapp

-
- -
-
Connected Extension: Not available
-

- - - -
- -
- -

-
Connected Accounts: Not available
-

- - -

- -

- -

-
- - + }); + handleEthSubscriptionDisplay(selectedScope) + } catch (error) { + console.error(error); + alert('Subscription failed!'); + } + } + + function updateConnectedAccountsDisplay() { + if (accounts.length > 0) { + document.getElementById('connectedAccounts').innerText = `Connected Accounts: ${accounts.join(', ')}`; + } else { + document.getElementById('connectedAccounts').innerText = `Connected Accounts: Not available`; + } + } + + function updateScopeSelectOptions() { + const select = document.getElementById('scopeSelect') + select.innerHTML = "" + + scopeStrings.forEach((scopeString) => { + const opt = document.createElement('option'); + opt.value = scopeString; + opt.innerHTML = scopeString; + select.appendChild(opt); + }) + } + + async function checkWalletConnection() { + try { + const { + sessionScopes + } = await extensionPortRequest({ + method: 'wallet_getSession' + }) + await onNewSessionScopes(sessionScopes) + + } catch (error) { + console.error('Failed to check wallet connection:', error); + } + } + + // DOM + document.getElementById('connectExtensionButton').addEventListener('click', connectExtension); + document.getElementById('sendTxButton').addEventListener('click', sendTransaction); + document.getElementById('subscribeButton').addEventListener('click', subscribeToBlockHeaders); + document.getElementById('connectButton').addEventListener('click', connectWallet); + document.getElementById('connectRequestFullButton').addEventListener('click', connectRequestFull); + document.getElementById('connectRequestMixedButton').addEventListener('click', connectRequestMixed); + document.getElementById('connectRequestOnlySendButton').addEventListener('click', connectRequestOnlySend); + document.getElementById('connectRequestOnlySubscribeButton').addEventListener('click', connectRequestOnlySubscribe); + From ea4881588521bb962d410363df8125f7d5233c90 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 30 Oct 2024 18:27:00 -0700 Subject: [PATCH 05/19] reorg multichain --- src/multichain_index.html | 355 +++++++++++++++++++------------------- 1 file changed, 179 insertions(+), 176 deletions(-) diff --git a/src/multichain_index.html b/src/multichain_index.html index 1a4acbc7..6e80b115 100644 --- a/src/multichain_index.html +++ b/src/multichain_index.html @@ -39,84 +39,8 @@

Ethereum Multichain API Dapp

let accounts = []; let scopeStrings = []; - // externally_connectable helpers - function generateJsonRpcId(opts) { - let max = Number.MAX_SAFE_INTEGER - jsonRpcId = jsonRpcId ?? Math.floor(Math.random() * max); - - jsonRpcId = jsonRpcId % max; - jsonRpcId += 1 - return jsonRpcId; - } - - async function extensionPortRequest(request) { - const id = generateJsonRpcId() - - extensionPort.postMessage({ - type: 'caip-x', - data: { - jsonrpc: "2.0", - id, - ...request - } - }) - - return new Promise((resolve, reject) => { - const listener = (msg) => { - if (msg.type === 'caip-x' && msg.data.id === id) { - const { - result, - error - } = msg.data - if (result) { - resolve(result) - } else { - reject(error) - } - extensionPort.onMessage.removeListener(listener) - } - } - extensionPort.onMessage.addListener(listener) - }) - } - - // Subscription DOM helpers - async function removeEthSubscriptionDisplay(scope) { - const scopeContainerId = `subscriptionsContainer-${scope}`; - const scopeContainer = document.getElementById(scopeContainerId) - if (scopeContainer) { - scopeContainer.remove() - } - } - - async function handleEthSubscriptionDisplay(scope, value = "Not available") { - const scopeContainerId = `subscriptionsContainer-${scope}`; - let scopeContainer = document.getElementById(scopeContainerId) - - if (!scopeContainer) { - const subscriptionsContainer = document.getElementById('subscriptionsContainer') - scopeContainer = document.createElement('div'); - scopeContainer.id = scopeContainerId - scopeContainer.style.display = "inline-block"; - scopeContainer.style.margin = "15px 15px 15px 0"; - scopeContainer.style.padding = "5px"; - scopeContainer.style.border = "1px solid black"; - subscriptionsContainer.appendChild(scopeContainer); - } - - scopeContainer.innerHTML = ` - ${scope} -
- Block Number: ${value} - ` - scopeContainer.style.border = "1px solid red"; - setTimeout(() => { - scopeContainer.style.border = "1px solid black"; - }, 1500) - } - - // Connecting to connection via externally_connectable + // Connection Initialization async function connectExtension() { const extensionId = document.getElementById('connectExtensionInput').value; try { @@ -136,7 +60,6 @@

Ethereum Multichain API Dapp

}) document.getElementById('connectedExtension').innerText = `Connected Extension: ${extensionId}`; - // Initial setup checkWalletConnection(); connectRequestFull(); } catch (error) { @@ -179,78 +102,7 @@

Ethereum Multichain API Dapp

}) } - // Connection DOM Helpers - function connectRequestFull() { - const params = { - optionalScopes: { - "eip155": { - "references": ["1", "59141", "11155111", "59144"], - "methods": ["personal_sign", "eth_signTypedData_v4", "wallet_watchAsset", "eth_sendTransaction", "eth_subscribe", "eth_unsubscribe"], - "notifications": ["eth_subscription"] - }, - } - } - - const textarea = document.getElementById('connectRequestTextarea') - textarea.value = JSON.stringify(params, null, 2) - } - - function connectRequestMixed() { - const params = { - optionalScopes: { - "eip155:1": { - "methods": ["eth_sendTransaction", "eth_subscribe"], - "notifications": ["eth_subscription"] - }, - "eip155:59141": { - "methods": ["eth_sendTransaction"], - "notifications": [] - }, - "eip155:11155111": { - "methods": ["eth_subscribe"], - "notifications": ["eth_subscription"] - }, - "eip155:59144": { - "methods": ["eth_call"], - "notifications": [] - }, - } - } - - const textarea = document.getElementById('connectRequestTextarea') - textarea.value = JSON.stringify(params, null, 2) - } - - function connectRequestOnlySend() { - const params = { - optionalScopes: { - "eip155": { - "references": ["1", "59141", "11155111", "59144"], - "methods": ["eth_sendTransaction"], - "notifications": [] - }, - } - } - - const textarea = document.getElementById('connectRequestTextarea') - textarea.value = JSON.stringify(params, null, 2) - } - - function connectRequestOnlySubscribe() { - const params = { - optionalScopes: { - "eip155": { - "references": ["1", "59141", "11155111", "59144"], - "methods": ["eth_subscribe"], - "notifications": ["eth_subscription"] - }, - } - } - - const textarea = document.getElementById('connectRequestTextarea') - textarea.value = JSON.stringify(params, null, 2) - } - + // Permission Initialization async function connectWallet() { if (!extensionPort) { alert('Connect to extension first.') @@ -274,6 +126,21 @@

Ethereum Multichain API Dapp

} } + async function checkWalletConnection() { + try { + const { + sessionScopes + } = await extensionPortRequest({ + method: 'wallet_getSession' + }) + await onNewSessionScopes(sessionScopes) + + } catch (error) { + console.error('Failed to check wallet connection:', error); + } + } + + // Transaction async function sendTransaction() { if (accounts.length === 0) { alert('Please connect your wallet first!'); @@ -307,6 +174,7 @@

Ethereum Multichain API Dapp

} } + // Subscription async function subscribeToBlockHeaders() { const selectedScope = document.getElementById('scopeSelect').value if (!selectedScope) { @@ -339,14 +207,17 @@

Ethereum Multichain API Dapp

} } - function updateConnectedAccountsDisplay() { - if (accounts.length > 0) { - document.getElementById('connectedAccounts').innerText = `Connected Accounts: ${accounts.join(', ')}`; - } else { - document.getElementById('connectedAccounts').innerText = `Connected Accounts: Not available`; - } - } + // DOM + document.getElementById('connectExtensionButton').addEventListener('click', connectExtension); + document.getElementById('sendTxButton').addEventListener('click', sendTransaction); + document.getElementById('subscribeButton').addEventListener('click', subscribeToBlockHeaders); + document.getElementById('connectButton').addEventListener('click', connectWallet); + document.getElementById('connectRequestFullButton').addEventListener('click', connectRequestFull); + document.getElementById('connectRequestMixedButton').addEventListener('click', connectRequestMixed); + document.getElementById('connectRequestOnlySendButton').addEventListener('click', connectRequestOnlySend); + document.getElementById('connectRequestOnlySubscribeButton').addEventListener('click', connectRequestOnlySubscribe); + // DOM Helpers function updateScopeSelectOptions() { const select = document.getElementById('scopeSelect') select.innerHTML = "" @@ -359,29 +230,161 @@

Ethereum Multichain API Dapp

}) } - async function checkWalletConnection() { - try { - const { - sessionScopes - } = await extensionPortRequest({ - method: 'wallet_getSession' - }) - await onNewSessionScopes(sessionScopes) + function updateConnectedAccountsDisplay() { + if (accounts.length > 0) { + document.getElementById('connectedAccounts').innerText = `Connected Accounts: ${accounts.join(', ')}`; + } else { + document.getElementById('connectedAccounts').innerText = `Connected Accounts: Not available`; + } + } - } catch (error) { - console.error('Failed to check wallet connection:', error); + async function removeEthSubscriptionDisplay(scope) { + const scopeContainerId = `subscriptionsContainer-${scope}`; + const scopeContainer = document.getElementById(scopeContainerId) + if (scopeContainer) { + scopeContainer.remove() } } - // DOM - document.getElementById('connectExtensionButton').addEventListener('click', connectExtension); - document.getElementById('sendTxButton').addEventListener('click', sendTransaction); - document.getElementById('subscribeButton').addEventListener('click', subscribeToBlockHeaders); - document.getElementById('connectButton').addEventListener('click', connectWallet); - document.getElementById('connectRequestFullButton').addEventListener('click', connectRequestFull); - document.getElementById('connectRequestMixedButton').addEventListener('click', connectRequestMixed); - document.getElementById('connectRequestOnlySendButton').addEventListener('click', connectRequestOnlySend); - document.getElementById('connectRequestOnlySubscribeButton').addEventListener('click', connectRequestOnlySubscribe); + async function handleEthSubscriptionDisplay(scope, value = "Not available") { + const scopeContainerId = `subscriptionsContainer-${scope}`; + let scopeContainer = document.getElementById(scopeContainerId) + + if (!scopeContainer) { + const subscriptionsContainer = document.getElementById('subscriptionsContainer') + scopeContainer = document.createElement('div'); + scopeContainer.id = scopeContainerId + scopeContainer.style.display = "inline-block"; + scopeContainer.style.margin = "15px 15px 15px 0"; + scopeContainer.style.padding = "5px"; + scopeContainer.style.border = "1px solid black"; + subscriptionsContainer.appendChild(scopeContainer); + } + + scopeContainer.innerHTML = ` + ${scope} +
+ Block Number: ${value} + ` + scopeContainer.style.border = "1px solid red"; + setTimeout(() => { + scopeContainer.style.border = "1px solid black"; + }, 1500) + } + + function connectRequestFull() { + const params = { + optionalScopes: { + "eip155": { + "references": ["1", "59141", "11155111", "59144"], + "methods": ["personal_sign", "eth_signTypedData_v4", "wallet_watchAsset", "eth_sendTransaction", "eth_subscribe", "eth_unsubscribe"], + "notifications": ["eth_subscription"] + }, + } + } + + const textarea = document.getElementById('connectRequestTextarea') + textarea.value = JSON.stringify(params, null, 2) + } + + function connectRequestMixed() { + const params = { + optionalScopes: { + "eip155:1": { + "methods": ["eth_sendTransaction", "eth_subscribe"], + "notifications": ["eth_subscription"] + }, + "eip155:59141": { + "methods": ["eth_sendTransaction"], + "notifications": [] + }, + "eip155:11155111": { + "methods": ["eth_subscribe"], + "notifications": ["eth_subscription"] + }, + "eip155:59144": { + "methods": ["eth_call"], + "notifications": [] + }, + } + } + + const textarea = document.getElementById('connectRequestTextarea') + textarea.value = JSON.stringify(params, null, 2) + } + + function connectRequestOnlySend() { + const params = { + optionalScopes: { + "eip155": { + "references": ["1", "59141", "11155111", "59144"], + "methods": ["eth_sendTransaction"], + "notifications": [] + }, + } + } + + const textarea = document.getElementById('connectRequestTextarea') + textarea.value = JSON.stringify(params, null, 2) + } + + function connectRequestOnlySubscribe() { + const params = { + optionalScopes: { + "eip155": { + "references": ["1", "59141", "11155111", "59144"], + "methods": ["eth_subscribe"], + "notifications": ["eth_subscription"] + }, + } + } + + const textarea = document.getElementById('connectRequestTextarea') + textarea.value = JSON.stringify(params, null, 2) + } + + + // externally_connectable helpers + function generateJsonRpcId(opts) { + let max = Number.MAX_SAFE_INTEGER + jsonRpcId = jsonRpcId ?? Math.floor(Math.random() * max); + + jsonRpcId = jsonRpcId % max; + jsonRpcId += 1 + return jsonRpcId; + } + + async function extensionPortRequest(request) { + const id = generateJsonRpcId() + + extensionPort.postMessage({ + type: 'caip-x', + data: { + jsonrpc: "2.0", + id, + ...request + } + }) + + return new Promise((resolve, reject) => { + const listener = (msg) => { + if (msg.type === 'caip-x' && msg.data.id === id) { + const { + result, + error + } = msg.data + if (result) { + resolve(result) + } else { + reject(error) + } + extensionPort.onMessage.removeListener(listener) + } + } + + extensionPort.onMessage.addListener(listener) + }) + } From d45773d6d47472199c3ecb1c366444cff75d2258 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 30 Oct 2024 20:21:22 -0700 Subject: [PATCH 06/19] cleanup reorg --- src/eip1193_index.html | 371 ++++++++++--------- src/multichain_index.html | 744 +++++++++++++++++++------------------- 2 files changed, 579 insertions(+), 536 deletions(-) diff --git a/src/eip1193_index.html b/src/eip1193_index.html index f5adfcff..e3545c1b 100644 --- a/src/eip1193_index.html +++ b/src/eip1193_index.html @@ -1,178 +1,211 @@ - - - - Ethereum EIP-1193 API Dapp - - -

Ethereum EIP-1193 API Dapp

- -
-
Connected Accounts: Not available
-

- - -

- -

-
Current Chain: Not available
-
Current Block Head: Not available
- - - + + diff --git a/src/multichain_index.html b/src/multichain_index.html index 6e80b115..00cff1ff 100644 --- a/src/multichain_index.html +++ b/src/multichain_index.html @@ -1,390 +1,400 @@ - - - - Ethereum Multichain API Dapp - - -

Ethereum Multichain API Dapp

- -
- -
-
Connected Extension: Not available
-

- - - - -
- -
- -

-
Connected Accounts: Not available
-

- - -

- -

- -

-
- - - + async function handleEthSubscriptionDisplay(scope, value = "Not available") { + const scopeContainerId = `subscriptionsContainer-${scope}`; + let scopeContainer = document.getElementById(scopeContainerId); + + if (!scopeContainer) { + const subscriptionsContainer = document.getElementById( + "subscriptionsContainer", + ); + scopeContainer = document.createElement("div"); + scopeContainer.id = scopeContainerId; + scopeContainer.style.display = "inline-block"; + scopeContainer.style.margin = "15px 15px 15px 0"; + scopeContainer.style.padding = "5px"; + scopeContainer.style.border = "1px solid black"; + subscriptionsContainer.appendChild(scopeContainer); + } + + scopeContainer.innerHTML = ` + ${scope} +
+ Block Number: ${value} + `; + scopeContainer.style.border = "1px solid red"; + setTimeout(() => { + scopeContainer.style.border = "1px solid black"; + }, 1500); + } + + function connectRequestFull() { + const params = { + optionalScopes: { + eip155: { + references: ["1", "59141", "11155111", "59144"], + methods: [ + "personal_sign", + "eth_signTypedData_v4", + "wallet_watchAsset", + "eth_sendTransaction", + "eth_subscribe", + "eth_unsubscribe", + ], + notifications: ["eth_subscription"], + }, + }, + }; + + const textarea = document.getElementById("connectRequestTextarea"); + textarea.value = JSON.stringify(params, null, 2); + } + + function connectRequestMixed() { + const params = { + optionalScopes: { + "eip155:1": { + methods: ["eth_sendTransaction", "eth_subscribe"], + notifications: ["eth_subscription"], + }, + "eip155:59141": { + methods: ["eth_sendTransaction"], + notifications: [], + }, + "eip155:11155111": { + methods: ["eth_subscribe"], + notifications: ["eth_subscription"], + }, + "eip155:59144": { + methods: ["eth_call"], + notifications: [], + }, + }, + }; + + const textarea = document.getElementById("connectRequestTextarea"); + textarea.value = JSON.stringify(params, null, 2); + } + + function connectRequestOnlySend() { + const params = { + optionalScopes: { + eip155: { + references: ["1", "59141", "11155111", "59144"], + methods: ["eth_sendTransaction"], + notifications: [], + }, + }, + }; + + const textarea = document.getElementById("connectRequestTextarea"); + textarea.value = JSON.stringify(params, null, 2); + } + + function connectRequestOnlySubscribe() { + const params = { + optionalScopes: { + eip155: { + references: ["1", "59141", "11155111", "59144"], + methods: ["eth_subscribe"], + notifications: ["eth_subscription"], + }, + }, + }; + + const textarea = document.getElementById("connectRequestTextarea"); + textarea.value = JSON.stringify(params, null, 2); + } + + // DOM Initialization + connectRequestFull(); + + From 8a4f0071fc0b0c40f17c2e0950255533f2889290 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 30 Oct 2024 20:23:29 -0700 Subject: [PATCH 07/19] one line alert --- src/eip1193_index.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/eip1193_index.html b/src/eip1193_index.html index e3545c1b..7ce9c8d5 100644 --- a/src/eip1193_index.html +++ b/src/eip1193_index.html @@ -44,9 +44,7 @@

Ethereum EIP-1193 API Dapp

window.ethereum.on("chainChanged", async (chainId) => { const selectedChainId = document.getElementById("chainSelect").value; if (chainId !== selectedChainId) { - alert( - `Wallet has changed the current chain to ${chainId}. Will try to change it back to ${selectedChainId} now.`, - ); + alert(`Wallet has changed the current chain to ${chainId}. Will try to change it back to ${selectedChainId} now.`); await switchChain(selectedChainId); } document.getElementById("blockHead").innerText = "Current Block Head: Not available"; From d53fcf83d7592ea8dfc068befa515a2b4471d9d4 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Wed, 30 Oct 2024 20:43:55 -0700 Subject: [PATCH 08/19] add button states to multichain --- src/multichain_index.html | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/multichain_index.html b/src/multichain_index.html index 00cff1ff..8342884c 100644 --- a/src/multichain_index.html +++ b/src/multichain_index.html @@ -35,6 +35,7 @@

Ethereum Multichain API Dapp

+ + + + + + + + diff --git a/src/multichain_demo_contract.sol b/src/multichain_demo_contract.sol new file mode 100644 index 00000000..7bca3cad --- /dev/null +++ b/src/multichain_demo_contract.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract RestrictedWithdrawal { + address public owner; + mapping(address => bool) public allowedAddresses; + uint256 public allowedCount; + + event Deposit(address indexed sender, uint256 amount); + event Withdrawal(address indexed recipient, uint256 amount); + event AllowedAddressUpdated(address indexed addr, bool allowed); + + modifier onlyOwner() { + require(msg.sender == owner, "Only owner can call this function"); + _; + } + + modifier onlyAllowed() { + require(allowedAddresses[msg.sender], "Not allowed to withdraw"); + _; + } + + constructor(address[] memory initialAllowedAddresses) { + owner = msg.sender; + for (uint256 i = 0; i < initialAllowedAddresses.length; i++) { + allowedAddresses[initialAllowedAddresses[i]] = true; + } + allowedCount = initialAllowedAddresses.length; + } + + function deposit() external payable { + require(msg.value > 0, "Must deposit non-zero amount"); + emit Deposit(msg.sender, msg.value); + } + + function withdraw(uint256 amount) external onlyAllowed { + require(address(this).balance >= amount, "Insufficient balance"); + payable(msg.sender).transfer(amount); + emit Withdrawal(msg.sender, amount); + } + + function updateAllowedAddress(address addr, bool allowed) external onlyOwner { + if (allowed && !allowedAddresses[addr]) { + allowedCount++; + } else if (!allowed && allowedAddresses[addr]) { + allowedCount--; + } + allowedAddresses[addr] = allowed; + emit AllowedAddressUpdated(addr, allowed); + } + + function getContractBalance() external view returns (uint256) { + return address(this).balance; + } +} diff --git a/src/multichain_demo_contract_abi.json b/src/multichain_demo_contract_abi.json new file mode 100644 index 00000000..38ccf501 --- /dev/null +++ b/src/multichain_demo_contract_abi.json @@ -0,0 +1,166 @@ +[ + { + "inputs": [ + { + "internalType": "address[]", + "name": "initialAllowedAddresses", + "type": "address[]" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "AllowedAddressUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allowedAddresses", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "allowedCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getContractBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "updateAllowedAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/multichain_demo_contract_bytecode.txt b/src/multichain_demo_contract_bytecode.txt new file mode 100644 index 00000000..a572e596 --- /dev/null +++ b/src/multichain_demo_contract_bytecode.txt @@ -0,0 +1 @@ +608060405234801561000f575f80fd5b50604051610d9b380380610d9b833981810160405281019061003191906102c0565b335f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f5b81518110156100f6576001805f84848151811061009257610091610307565b5b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508080600101915050610072565b50805160028190555050610334565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6101608261011a565b810181811067ffffffffffffffff8211171561017f5761017e61012a565b5b80604052505050565b5f610191610105565b905061019d8282610157565b919050565b5f67ffffffffffffffff8211156101bc576101bb61012a565b5b602082029050602081019050919050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101fa826101d1565b9050919050565b61020a816101f0565b8114610214575f80fd5b50565b5f8151905061022581610201565b92915050565b5f61023d610238846101a2565b610188565b905080838252602082019050602084028301858111156102605761025f6101cd565b5b835b8181101561028957806102758882610217565b845260208401935050602081019050610262565b5050509392505050565b5f82601f8301126102a7576102a6610116565b5b81516102b784826020860161022b565b91505092915050565b5f602082840312156102d5576102d461010e565b5b5f82015167ffffffffffffffff8111156102f2576102f1610112565b5b6102fe84828501610293565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b610a5a806103415f395ff3fe60806040526004361061006f575f3560e01c80636f9fb98a1161004d5780636f9fb98a146100ff57806389d34e4b146101295780638da5cb5b14610153578063d0e30db01461017d5761006f565b80630e73684e146100735780632e1a7d4d1461009b5780634120657a146100c3575b5f80fd5b34801561007e575f80fd5b5061009960048036038101906100949190610674565b610187565b005b3480156100a6575f80fd5b506100c160048036038101906100bc91906106e5565b6103a1565b005b3480156100ce575f80fd5b506100e960048036038101906100e49190610710565b610502565b6040516100f6919061074a565b60405180910390f35b34801561010a575f80fd5b5061011361051f565b6040516101209190610772565b60405180910390f35b348015610134575f80fd5b5061013d610526565b60405161014a9190610772565b60405180910390f35b34801561015e575f80fd5b5061016761052c565b604051610174919061079a565b60405180910390f35b61018561054f565b005b5f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610214576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161020b90610833565b60405180910390fd5b808015610268575060015f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16155b156102895760025f81548092919061027f9061087e565b91905055506102fb565b801580156102dd575060015f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff165b156102fa5760025f8154809291906102f4906108c5565b91905055505b5b8060015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff167f14fcc60af8440546881d552eb01644aa747a1106cb912daf1a3b52e9932ed18982604051610395919061074a565b60405180910390a25050565b60015f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff1661042a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161042190610936565b60405180910390fd5b8047101561046d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104649061099e565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f193505050501580156104b0573d5f803e3d5ffd5b503373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040516104f79190610772565b60405180910390a250565b6001602052805f5260405f205f915054906101000a900460ff1681565b5f47905090565b60025481565b5f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f3411610591576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161058890610a06565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040516105d79190610772565b60405180910390a2565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61060e826105e5565b9050919050565b61061e81610604565b8114610628575f80fd5b50565b5f8135905061063981610615565b92915050565b5f8115159050919050565b6106538161063f565b811461065d575f80fd5b50565b5f8135905061066e8161064a565b92915050565b5f806040838503121561068a576106896105e1565b5b5f6106978582860161062b565b92505060206106a885828601610660565b9150509250929050565b5f819050919050565b6106c4816106b2565b81146106ce575f80fd5b50565b5f813590506106df816106bb565b92915050565b5f602082840312156106fa576106f96105e1565b5b5f610707848285016106d1565b91505092915050565b5f60208284031215610725576107246105e1565b5b5f6107328482850161062b565b91505092915050565b6107448161063f565b82525050565b5f60208201905061075d5f83018461073b565b92915050565b61076c816106b2565b82525050565b5f6020820190506107855f830184610763565b92915050565b61079481610604565b82525050565b5f6020820190506107ad5f83018461078b565b92915050565b5f82825260208201905092915050565b7f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f5f8201527f6e00000000000000000000000000000000000000000000000000000000000000602082015250565b5f61081d6021836107b3565b9150610828826107c3565b604082019050919050565b5f6020820190508181035f83015261084a81610811565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610888826106b2565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036108ba576108b9610851565b5b600182019050919050565b5f6108cf826106b2565b91505f82036108e1576108e0610851565b5b600182039050919050565b7f4e6f7420616c6c6f77656420746f2077697468647261770000000000000000005f82015250565b5f6109206017836107b3565b915061092b826108ec565b602082019050919050565b5f6020820190508181035f83015261094d81610914565b9050919050565b7f496e73756666696369656e742062616c616e63650000000000000000000000005f82015250565b5f6109886014836107b3565b915061099382610954565b602082019050919050565b5f6020820190508181035f8301526109b58161097c565b9050919050565b7f4d757374206465706f736974206e6f6e2d7a65726f20616d6f756e74000000005f82015250565b5f6109f0601c836107b3565b91506109fb826109bc565b602082019050919050565b5f6020820190508181035f830152610a1d816109e4565b905091905056fea26469706673582212201f27d5fba0ee0a42072da352d8c8e74f51761b2d0728ddfa2279b5da8848454664736f6c634300081a0033 From fa9ce96cc2cbad206c940551ea7e22e27032fca2 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 31 Oct 2024 14:33:00 -0700 Subject: [PATCH 10/19] WIP add connection --- src/multichain_demo.html | 597 ++++++++++++++++++++++++++++----------- 1 file changed, 427 insertions(+), 170 deletions(-) diff --git a/src/multichain_demo.html b/src/multichain_demo.html index e7bc0b39..183cce14 100644 --- a/src/multichain_demo.html +++ b/src/multichain_demo.html @@ -16,7 +16,7 @@ .modal { display: none; position: fixed; - z-index: 1; + z-index: 2; left: 0; top: 0; width: 100%; @@ -45,10 +45,37 @@ text-decoration: none; cursor: pointer; } + .floating-div { + position: absolute; + top: 0; + right: 0; + width: 150px; + background-color: lightgray; + z-index: 1; + padding: 5px; + } + +
+ +
+ + +

+ Sepolia +
+ Linea Sepolia +
+ Arbitrum Sepolia +
+ OP Sepolia +
+ Blast Sepolia +
+ - + } + + // + // D3 + // + function generateLabel() { + return accounts.map(account => `${account}: ${(Math.random() * 10).toFixed(3)}`).join('\n'); + } + + const svg = d3.select("body").append("svg") + .attr("width", window.innerWidth) + .attr("height", window.innerHeight); + + const simulation = d3.forceSimulation(nodes) + .force("link", d3.forceLink(links).id(d => d.id).distance(400)) // Further increased distance + .force("charge", d3.forceManyBody().strength(-1600)) // Further increased negative strength + .force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2)) + .force("collide", d3.forceCollide().radius(d => getRadius(d.label) * NODE_SCALE + NODE_PADDING + 40)); // Further increased radius + + const link = svg.append("g") + .attr("class", "links") + .selectAll("line") + .data(links) + .enter().append("line") + .attr("stroke-width", 2) + .attr("stroke", "#999") + .on("click", function(event, d) { + const sourceNode = nodes.find(node => node.id === d.source.id); + const targetNode = nodes.find(node => node.id === d.target.id); + showModal(`Source: ${sourceNode.label}\nTarget: ${targetNode.label}`); + }); + + const node = svg.append("g") + .attr("class", "nodes") + .selectAll("g") + .data(nodes) + .enter().append("g") + .on("click", function(event, d) { + showModal(d.label); + }); + + node.append("circle") + .attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING) + .attr("fill", () => pastelColors[Math.floor(Math.random() * pastelColors.length)]); + + const text = node.append("text") + .attr("text-anchor", "middle"); + + text.selectAll("tspan") + .data(d => d.label.split('\n')) + .enter() + .append("tspan") + .attr("x", 0) + .attr("dy", (d, i) => i === 0 ? 0 : "1.2em") + .text(d => d); + + simulation.on("tick", () => { + link + .attr("x1", d => d.source.x) + .attr("y1", d => d.source.y) + .attr("x2", d => d.target.x) + .attr("y2", d => d.target.y); + + node + .attr("transform", d => `translate(${d.x},${d.y})`); + }); + + + function getRadius(text) { + const context = document.createElement("canvas").getContext("2d"); + context.font = "10px sans-serif"; + const lines = text.split("\n"); + const maxWidth = Math.max(...lines.map(line => context.measureText(line).width)); + const lineHeight = 10; // Approximate line height + const textHeight = lines.length * lineHeight; + return Math.max(20, Math.sqrt(maxWidth * maxWidth + textHeight * textHeight) / 2 + 10); // Ensure a minimum radius of 20 + } + + window.addEventListener("resize", () => { + svg.attr("width", window.innerWidth) + .attr("height", window.innerHeight); + simulation.force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2)); + simulation.alpha(1).restart(); // Restart simulation to adjust positions + }); + + // Modal functionality + const modal = document.getElementById("myModal"); + const modalText = document.getElementById("modal-text"); + const span = document.getElementsByClassName("close")[0]; + + function showModal(content) { + modalText.textContent = content; + modal.style.display = "block"; + } + + span.onclick = function() { + modal.style.display = "none"; + } + + window.onclick = function(event) { + if (event.target == modal) { + modal.style.display = "none"; + } + } + + // setTimeout(() => { + // nodes[0].label = generateLabel(); + // text.selectAll("tspan").remove(); + // text.selectAll("tspan") + // .data(d => d.label.split('\n')) + // .enter() + // .append("tspan") + // .attr("x", 0) + // .attr("dy", (d, i) => i === 0 ? 0 : "1.2em") + // .text(d => d); + // node.select("circle").attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING); + // simulation.alpha(1).restart(); // Restart simulation to adjust positions + // }, 3000); + + // Flash an edge + // setTimeout(() => { + // const randomLink = d3.select(link.nodes()[Math.floor(Math.random() * links.length)]); + // const originalColor = randomLink.attr("stroke"); + // randomLink.attr("stroke", "green"); + // setTimeout(() => { + // randomLink.attr("stroke", originalColor); + // }, 3000); + // }, 5000); + + + // setTimeout(() => { + // const randomLink = links[Math.floor(Math.random() * links.length)]; + // const linkSelection = d3.select(link.nodes()[links.indexOf(randomLink)]); + // const sourceNode = nodes.find(node => node.id === randomLink.source.id); + // const targetNode = nodes.find(node => node.id === randomLink.target.id); + + // const edgeLabel = svg.append("text") + // .attr("x", (sourceNode.x + targetNode.x) / 2) + // .attr("y", (sourceNode.y + targetNode.y) / 2) + // .attr("dy", -5) + // .attr("text-anchor", "middle") + // .attr("fill", "green") + // .text("sending..."); + + // setTimeout(() => { + // edgeLabel.remove(); + // }, 3000); + // }, 5000); + + + function addNode(id, label) { + const newNode = { id, label: generateLabel() }; + nodes.push(newNode); + + // TODO: GET RID OF THIS + if (nodes.length) { + const randomNode = nodes[Math.floor(Math.random() * (nodes.length - 1))]; + links.push({ source: randomNode.id, target: newNode.id }); + } + + // Update the simulation with the new node and link + simulation.nodes(nodes); + simulation.force("link").links(links); + + // Add the new node to the SVG + const newNodeSelection = svg.select(".nodes") + .selectAll("g") + .data(nodes) + .enter() + .append("g") + .on("click", function(event, d) { + showModal(d.label); + }); + + newNodeSelection.append("circle") + .attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING) + .attr("fill", () => pastelColors[Math.floor(Math.random() * pastelColors.length)]); + + const newText = newNodeSelection.append("text") + .attr("text-anchor", "middle"); + + newText.selectAll("tspan") + .data(d => d.label.split('\n')) + .enter() + .append("tspan") + .attr("x", 0) + .attr("dy", (d, i) => i === 0 ? 0 : "1.2em") + .text(d => d); + + // Restart the simulation + simulation.alpha(1).restart(); + } + + function removeNode(id) { + nodes = nodes.filter(node => node.id !== randomNode.id); + links = links.filter(link => link.source.id !== randomNode.id && link.target.id !== randomNode.id); + + // Update the simulation with the removed node and links + simulation.nodes(nodes); + simulation.force("link").links(links); + + // Remove the node and its edges from the SVG + svg.select(".nodes").selectAll("g") + .data(nodes, d => d.id) + .exit().remove(); + + svg.select(".links").selectAll("line") + .data(links, d => `${d.source.id}-${d.target.id}`) + .exit().remove(); + + // Restart the simulation + simulation.alpha(1).restart(); + } + + // DOM + document.getElementById("connectExtensionButton").addEventListener("click", connectExtension); + document.getElementById("connectButton").addEventListener("click", connectWallet); +}); + From 3d5faf436a8c01a95cb8b3795c28fd9502e5e271 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 31 Oct 2024 16:21:19 -0700 Subject: [PATCH 11/19] balance loop + bridge constants --- src/multichain_demo.html | 376 ++++++++++++++++++++++++++++----------- 1 file changed, 272 insertions(+), 104 deletions(-) diff --git a/src/multichain_demo.html b/src/multichain_demo.html index 183cce14..5a13d01b 100644 --- a/src/multichain_demo.html +++ b/src/multichain_demo.html @@ -90,10 +90,31 @@ // Constants // const BridgeContracts = { - 'eip155:5': { - contractAddress: '0xdead', - sendsTo: ['eip155:111555111'] - } + // Sepolia + "eip155:11155111": { + contractAddress: '0xdead', + supports: ["eip155:59141", "eip155:421614", "eip155:11155420", "eip155:168587773"] + }, + // Linea Sepolia (no Arbitrium) + "eip155:59141": { + contractAddress: '0xdead', + supports: ["eip155:11155111", "eip155:11155420", "eip155:168587773"] + }, + // Arbitrum Sepolia (no Linea) + "eip155:421614": { + contractAddress: '0xdead', + supports: ["eip155:11155111", "eip155:11155420", "eip155:168587773"] + }, + // OP Sepolia (no Blast) + "eip155:11155420": { + contractAddress: '0xdead', + supports: ["eip155:11155111", "eip155:59141", "eip155:421614"] + }, + // Blast Sepolia (no OP) + "eip155:168587773": { + contractAddress: '0xdead', + supports: ["eip155:11155111", "eip155:59141", "eip155:421614"] + }, } const pastelColors = [ @@ -103,7 +124,7 @@ "#e5c28c", "#e8d097", "#edd9aa", "#f2e6bb", "#f9f3dd" ]; const NODE_PADDING = 20; - const NODE_SCALE = 1.33; + const NODE_SCALE = 2; // // State @@ -114,6 +135,8 @@ let accounts = []; let scopeStrings = []; + let balances = {} + let nodes = []; let links = []; for (let i = 0; i < nodes.length; i++) { @@ -131,46 +154,52 @@ async function connectExtension() { const extensionId = document.getElementById("connectExtensionInput").value; try { - extensionPort = chrome.runtime.connect(extensionId); // externally_connectable - extensionPort.onMessage.addListener((msg) => { - const { data: { method, params } } = msg; - // Subscription Events - if (method === "wallet_notify") { - handleEthSubscriptionDisplay( - params.scope, - params.notification.params.result.number, - ); - // Permission Events - } else if (method === "wallet_sessionChanged") { - onNewSessionScopes(params.sessionScopes); - } - console.log(msg.data); - }); + extensionPort = chrome.runtime.connect(extensionId); // externally_connectable + extensionPort.onMessage.addListener((msg) => { + const { data: { method, params } } = msg; + // Subscription Events + if (method === "wallet_notify") { + handleEthSubscriptionDisplay( + params.scope, + params.notification.params.result.number, + ); + // Permission Events + } else if (method === "wallet_sessionChanged") { + onNewSessionScopes(params.sessionScopes); + } + console.log(msg.data); + }); - // Dapp Initialization - checkWalletConnection(); + // Dapp Initialization + checkWalletConnection(); } catch (error) { - console.error(error); - alert("Failed to connect to extension!"); + console.error(error); + alert("Failed to connect to extension!"); } } // Permission Handling async function onNewSessionScopes(sessionScopes) { const oldScopeStrings = scopeStrings; - const eip155ScopeStrings = Object.keys(sessionScopes).filter((scopeString) => - /eip155:[0-9]+/u.test(scopeString), - ); + const eip155ScopeStrings = Object.keys(sessionScopes).filter((scopeString) => { + // return /eip155:[0-9]+/u.test(scopeString) + if (!BridgeContracts[scopeString]) { + console.log(`ignoring ${scopeString} since it is not defined in BridgeContracts`) + return false + } + return true + }); const eip155AccountsSet = new Set(); eip155ScopeStrings.forEach((scopeString) => { - const scopeObject = sessionScopes[scopeString]; - scopeObject.accounts.forEach((account) => { - const address = account.split(":")[2]; - eip155AccountsSet.add(address); - }); + const scopeObject = sessionScopes[scopeString]; + + scopeObject.accounts.forEach((account) => { + const address = account.split(":")[2]; + eip155AccountsSet.add(address); + }); }); currentSessionScopes = sessionScopes; @@ -189,51 +218,60 @@ }); scopeStrings.forEach(newScopeString => { if (!oldScopeStrings.includes(newScopeString)) { - addNode(newScopeString, 'HELLO') + addNode(newScopeString) } }) - console.log({nodes, links}) + startPolling() + } + + function getNodeLabelText(scopeString) { + const accountsText = accounts.map(account => { + const balance = balances[scopeString]?.[account] || 0 + return `${account.slice(0,7)}...${account.slice(37,42)}: ${balance.toFixed(4)}` + }).join('\n') + return`${scopeString}\n----------\n${accountsText}` } // Permission Initialization async function connectWallet() { if (!extensionPort) { - alert("Connect to extension first."); - return; + alert("Connect to extension first."); + return; } try { const { sessionScopes } = await extensionPortRequest({ - method: "wallet_createSession", - params: { - eip155: { - references: ["11155111", "59141", "421614", "11155420", "168587773"], - methods: [ - "eth_sendTransaction", - "eth_subscribe", - ], - notifications: ["eth_subscription"], + method: "wallet_createSession", + params: { + eip155: { + references: ["11155111", "59141", "421614", "11155420", "168587773"], + methods: [ + "eth_getBalance", + "eth_sendTransaction", + "eth_subscribe", + ], + notifications: ["eth_subscription"], + }, }, - }, - }); + }); - await onNewSessionScopes(sessionScopes); + await onNewSessionScopes(sessionScopes); } catch (error) { - console.error(error); - alert("Failed to connect wallet!"); + console.error(error); + alert("Failed to connect wallet!"); } } async function checkWalletConnection() { try { - const { sessionScopes } = await extensionPortRequest({ - method: "wallet_getSession", - }); - await onNewSessionScopes(sessionScopes); + const { sessionScopes } = await extensionPortRequest({ + method: "wallet_getSession", + }); + await onNewSessionScopes(sessionScopes); } catch (error) { - console.error("Failed to check wallet connection:", error); - alert("Failed to check wallet connection"); + console.error("Failed to check wallet connection:", error); + alert("Failed to check wallet connection"); } } @@ -256,24 +294,24 @@ const id = generateJsonRpcId(); extensionPort.postMessage({ - type: "caip-x", - data: { - jsonrpc: "2.0", - id, - ...request, - }, + type: "caip-x", + data: { + jsonrpc: "2.0", + id, + ...request, + }, }); return new Promise((resolve, reject) => { const listener = (msg) => { if (msg.type === "caip-x" && msg.data.id === id) { - const { result, error } = msg.data; - if (result) { - resolve(result); - } else { - reject(error); - } - extensionPort.onMessage.removeListener(listener); + const { result, error } = msg.data; + if (result) { + resolve(result); + } else { + reject(error); + } + extensionPort.onMessage.removeListener(listener); } }; @@ -298,7 +336,7 @@ .force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2)) .force("collide", d3.forceCollide().radius(d => getRadius(d.label) * NODE_SCALE + NODE_PADDING + 40)); // Further increased radius - const link = svg.append("g") + let link = svg.append("g") .attr("class", "links") .selectAll("line") .data(links) @@ -311,7 +349,7 @@ showModal(`Source: ${sourceNode.label}\nTarget: ${targetNode.label}`); }); - const node = svg.append("g") + let node = svg.append("g") .attr("class", "nodes") .selectAll("g") .data(nodes) @@ -324,8 +362,9 @@ .attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING) .attr("fill", () => pastelColors[Math.floor(Math.random() * pastelColors.length)]); - const text = node.append("text") - .attr("text-anchor", "middle"); + let text = node.append("text") + .attr("text-anchor", "middle") + .attr("dominant-baseline", "top"); // this may need to be added elsewhere text.selectAll("tspan") .data(d => d.label.split('\n')) @@ -335,6 +374,49 @@ .attr("dy", (d, i) => i === 0 ? 0 : "1.2em") .text(d => d); + + function updateGraph() { + d3.selectAll("g > *").remove() + + link = svg.append("g") + .attr("class", "links") + .selectAll("line") + .data(links) + .enter().append("line") + .attr("stroke-width", 2) + .attr("stroke", "#999") + .on("click", function(event, d) { + const sourceNode = nodes.find(node => node.id === d.source.id); + const targetNode = nodes.find(node => node.id === d.target.id); + showModal(`Source: ${sourceNode.label}\nTarget: ${targetNode.label}`); + }); + + node = svg.append("g") + .attr("class", "nodes") + .selectAll("g") + .data(nodes) + .enter().append("g") + .on("click", function(event, d) { + showModal(d.label); + }); + + node.append("circle") + .attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING) + .attr("fill", () => pastelColors[Math.floor(Math.random() * pastelColors.length)]); + + text = node.append("text") + .attr("text-anchor", "middle") + .attr("dominant-baseline", "top"); // this may need to be added elsewhere + + text.selectAll("tspan") + .data(d => d.label.split('\n')) + .enter() + .append("tspan") + .attr("x", 0) + .attr("dy", (d, i) => i === 0 ? 0 : "1.2em") + .text(d => d); + } + simulation.on("tick", () => { link .attr("x1", d => d.source.x) @@ -429,52 +511,63 @@ // }, 5000); - function addNode(id, label) { - const newNode = { id, label: generateLabel() }; + function addNode(id) { + console.log('adding node', id) + const newNode = { id, label: getNodeLabelText(id) }; nodes.push(newNode); - // TODO: GET RID OF THIS - if (nodes.length) { - const randomNode = nodes[Math.floor(Math.random() * (nodes.length - 1))]; - links.push({ source: randomNode.id, target: newNode.id }); - } + const supportedScopes = BridgeContracts[id]?.supports || [] + + supportedScopes.forEach((supportedScope) => { + const targetNode = nodes.find(node => node.id === supportedScope) + const targetLink = links.find(link => + (link.source.id === id && link.target.id === supportedScope) || + (link.source.id === supportedScope && link.target.id === id) + ) + if (targetNode && !targetLink) { + links.push({ source: id, target: targetNode.id }); + } + }) // Update the simulation with the new node and link simulation.nodes(nodes); simulation.force("link").links(links); // Add the new node to the SVG - const newNodeSelection = svg.select(".nodes") - .selectAll("g") - .data(nodes) - .enter() - .append("g") - .on("click", function(event, d) { - showModal(d.label); - }); - - newNodeSelection.append("circle") - .attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING) - .attr("fill", () => pastelColors[Math.floor(Math.random() * pastelColors.length)]); - - const newText = newNodeSelection.append("text") - .attr("text-anchor", "middle"); + // const newNodeSelection = svg.select(".nodes") + // .selectAll("g") + // .data(nodes) + // .enter() + // .append("g") + // .on("click", function(event, d) { + // showModal(d.label); + // }); + + // newNodeSelection.append("circle") + // .attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING) + // .attr("fill", () => pastelColors[Math.floor(Math.random() * pastelColors.length)]); + + // const newText = newNodeSelection.append("text") + // .attr("text-anchor", "middle"); + + // newText.selectAll("tspan") + // .data(d => d.label.split('\n')) + // .enter() + // .append("tspan") + // .attr("x", 0) + // .attr("dy", (d, i) => i === 0 ? 0 : "1.2em") + // .text(d => d); - newText.selectAll("tspan") - .data(d => d.label.split('\n')) - .enter() - .append("tspan") - .attr("x", 0) - .attr("dy", (d, i) => i === 0 ? 0 : "1.2em") - .text(d => d); // Restart the simulation + updateGraph() simulation.alpha(1).restart(); } function removeNode(id) { - nodes = nodes.filter(node => node.id !== randomNode.id); - links = links.filter(link => link.source.id !== randomNode.id && link.target.id !== randomNode.id); + console.log('removing node', id) + nodes = nodes.filter(node => node.id !== id); + links = links.filter(link => link.source.id !== id && link.target.id !== id); // Update the simulation with the removed node and links simulation.nodes(nodes); @@ -489,13 +582,88 @@ .data(links, d => `${d.source.id}-${d.target.id}`) .exit().remove(); + updateGraph() // Restart the simulation simulation.alpha(1).restart(); } + function updateNode(id) { + nodes.find(node => node.id === id).label = getNodeLabelText(id) + text.selectAll("tspan").remove(); + text.selectAll("tspan") + .data(d => d.label.split('\n')) + .enter() + .append("tspan") + .attr("x", 0) + .attr("dy", (d, i) => i === 0 ? 0 : "1.2em") + .text(d => d); + node.select("circle").attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING); + + // Restart simulation to adjust positions + simulation.alpha(1).restart(); + } + // DOM document.getElementById("connectExtensionButton").addEventListener("click", connectExtension); document.getElementById("connectButton").addEventListener("click", connectWallet); + + // Loops + async function updateAccountBalances() { + if(!extensionPort) { + return + } + if(!accounts.length) { + return + } + + for (let account of accounts) { + for (let scopeString of scopeStrings) { + // console.log(`updating balance for account ${account} on scope ${scopeString}`) + try { + const balance = await extensionPortRequest({ + method: "wallet_invokeMethod", + params: { + scope: scopeString, + request: { + "method": "eth_getBalance", + "params": [ + account, + "latest" + ], + }, + }, + }) + balances[scopeString] = balances[scopeString] || {} + const oldBalance = balances[scopeString][account] || 0 + balances[scopeString][account] = parseInt(balance, 16) / Math.pow(10, 18); + + if (oldBalance !== balances[scopeString][account]) { + console.log(`updating node for account ${account} on scope ${scopeString} with ${balances[scopeString][account]}`) + updateNode(scopeString) + } + + } catch (error) { + console.error(`failed updating balance for account ${account} on scope ${scopeString}`, error) + } + } + } + } + + async function updateAccountBalancesPoll() { + await updateAccountBalances() + setTimeout(() => { + updateAccountBalancesPoll() + }, 10000) + } + + let isPolling; + function startPolling() { + if (!isPolling) { + isPolling = true + updateAccountBalancesPoll() + } + } + }); From 868715a0ae5ef75065980f184f8e631f5d4d4bc6 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 31 Oct 2024 18:31:48 -0700 Subject: [PATCH 12/19] Fix contract --- src/multichain_demo_contract.sol | 5 ++--- src/multichain_demo_contract_abi.json | 11 ++++------- src/multichain_demo_contract_bytecode.txt | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/multichain_demo_contract.sol b/src/multichain_demo_contract.sol index 7bca3cad..9e84a446 100644 --- a/src/multichain_demo_contract.sol +++ b/src/multichain_demo_contract.sol @@ -28,9 +28,8 @@ contract RestrictedWithdrawal { allowedCount = initialAllowedAddresses.length; } - function deposit() external payable { - require(msg.value > 0, "Must deposit non-zero amount"); - emit Deposit(msg.sender, msg.value); + receive() external payable { + emit Deposit(msg.sender, msg.value); } function withdraw(uint256 amount) external onlyAllowed { diff --git a/src/multichain_demo_contract_abi.json b/src/multichain_demo_contract_abi.json index 38ccf501..48b9f99a 100644 --- a/src/multichain_demo_contract_abi.json +++ b/src/multichain_demo_contract_abi.json @@ -99,13 +99,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "deposit", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, { "inputs": [], "name": "getContractBalance", @@ -162,5 +155,9 @@ "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" } ] diff --git a/src/multichain_demo_contract_bytecode.txt b/src/multichain_demo_contract_bytecode.txt index a572e596..2d0daa1c 100644 --- a/src/multichain_demo_contract_bytecode.txt +++ b/src/multichain_demo_contract_bytecode.txt @@ -1 +1 @@ -608060405234801561000f575f80fd5b50604051610d9b380380610d9b833981810160405281019061003191906102c0565b335f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f5b81518110156100f6576001805f84848151811061009257610091610307565b5b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508080600101915050610072565b50805160028190555050610334565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6101608261011a565b810181811067ffffffffffffffff8211171561017f5761017e61012a565b5b80604052505050565b5f610191610105565b905061019d8282610157565b919050565b5f67ffffffffffffffff8211156101bc576101bb61012a565b5b602082029050602081019050919050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101fa826101d1565b9050919050565b61020a816101f0565b8114610214575f80fd5b50565b5f8151905061022581610201565b92915050565b5f61023d610238846101a2565b610188565b905080838252602082019050602084028301858111156102605761025f6101cd565b5b835b8181101561028957806102758882610217565b845260208401935050602081019050610262565b5050509392505050565b5f82601f8301126102a7576102a6610116565b5b81516102b784826020860161022b565b91505092915050565b5f602082840312156102d5576102d461010e565b5b5f82015167ffffffffffffffff8111156102f2576102f1610112565b5b6102fe84828501610293565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b610a5a806103415f395ff3fe60806040526004361061006f575f3560e01c80636f9fb98a1161004d5780636f9fb98a146100ff57806389d34e4b146101295780638da5cb5b14610153578063d0e30db01461017d5761006f565b80630e73684e146100735780632e1a7d4d1461009b5780634120657a146100c3575b5f80fd5b34801561007e575f80fd5b5061009960048036038101906100949190610674565b610187565b005b3480156100a6575f80fd5b506100c160048036038101906100bc91906106e5565b6103a1565b005b3480156100ce575f80fd5b506100e960048036038101906100e49190610710565b610502565b6040516100f6919061074a565b60405180910390f35b34801561010a575f80fd5b5061011361051f565b6040516101209190610772565b60405180910390f35b348015610134575f80fd5b5061013d610526565b60405161014a9190610772565b60405180910390f35b34801561015e575f80fd5b5061016761052c565b604051610174919061079a565b60405180910390f35b61018561054f565b005b5f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610214576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161020b90610833565b60405180910390fd5b808015610268575060015f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16155b156102895760025f81548092919061027f9061087e565b91905055506102fb565b801580156102dd575060015f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff165b156102fa5760025f8154809291906102f4906108c5565b91905055505b5b8060015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff167f14fcc60af8440546881d552eb01644aa747a1106cb912daf1a3b52e9932ed18982604051610395919061074a565b60405180910390a25050565b60015f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff1661042a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161042190610936565b60405180910390fd5b8047101561046d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104649061099e565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f193505050501580156104b0573d5f803e3d5ffd5b503373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040516104f79190610772565b60405180910390a250565b6001602052805f5260405f205f915054906101000a900460ff1681565b5f47905090565b60025481565b5f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f3411610591576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161058890610a06565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040516105d79190610772565b60405180910390a2565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61060e826105e5565b9050919050565b61061e81610604565b8114610628575f80fd5b50565b5f8135905061063981610615565b92915050565b5f8115159050919050565b6106538161063f565b811461065d575f80fd5b50565b5f8135905061066e8161064a565b92915050565b5f806040838503121561068a576106896105e1565b5b5f6106978582860161062b565b92505060206106a885828601610660565b9150509250929050565b5f819050919050565b6106c4816106b2565b81146106ce575f80fd5b50565b5f813590506106df816106bb565b92915050565b5f602082840312156106fa576106f96105e1565b5b5f610707848285016106d1565b91505092915050565b5f60208284031215610725576107246105e1565b5b5f6107328482850161062b565b91505092915050565b6107448161063f565b82525050565b5f60208201905061075d5f83018461073b565b92915050565b61076c816106b2565b82525050565b5f6020820190506107855f830184610763565b92915050565b61079481610604565b82525050565b5f6020820190506107ad5f83018461078b565b92915050565b5f82825260208201905092915050565b7f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f5f8201527f6e00000000000000000000000000000000000000000000000000000000000000602082015250565b5f61081d6021836107b3565b9150610828826107c3565b604082019050919050565b5f6020820190508181035f83015261084a81610811565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610888826106b2565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036108ba576108b9610851565b5b600182019050919050565b5f6108cf826106b2565b91505f82036108e1576108e0610851565b5b600182039050919050565b7f4e6f7420616c6c6f77656420746f2077697468647261770000000000000000005f82015250565b5f6109206017836107b3565b915061092b826108ec565b602082019050919050565b5f6020820190508181035f83015261094d81610914565b9050919050565b7f496e73756666696369656e742062616c616e63650000000000000000000000005f82015250565b5f6109886014836107b3565b915061099382610954565b602082019050919050565b5f6020820190508181035f8301526109b58161097c565b9050919050565b7f4d757374206465706f736974206e6f6e2d7a65726f20616d6f756e74000000005f82015250565b5f6109f0601c836107b3565b91506109fb826109bc565b602082019050919050565b5f6020820190508181035f830152610a1d816109e4565b905091905056fea26469706673582212201f27d5fba0ee0a42072da352d8c8e74f51761b2d0728ddfa2279b5da8848454664736f6c634300081a0033 +608060405234801561001057600080fd5b50604051610d39380380610d39833981810160405281019061003291906102d6565b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060005b81518110156100fc5760018060008484815181106100965761009561031f565b5b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508080600101915050610075565b5080516002819055505061034e565b6000604051905090565b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61016d82610124565b810181811067ffffffffffffffff8211171561018c5761018b610135565b5b80604052505050565b600061019f61010b565b90506101ab8282610164565b919050565b600067ffffffffffffffff8211156101cb576101ca610135565b5b602082029050602081019050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061020c826101e1565b9050919050565b61021c81610201565b811461022757600080fd5b50565b60008151905061023981610213565b92915050565b600061025261024d846101b0565b610195565b90508083825260208201905060208402830185811115610275576102746101dc565b5b835b8181101561029e578061028a888261022a565b845260208401935050602081019050610277565b5050509392505050565b600082601f8301126102bd576102bc61011f565b5b81516102cd84826020860161023f565b91505092915050565b6000602082840312156102ec576102eb610115565b5b600082015167ffffffffffffffff81111561030a5761030961011a565b5b610316848285016102a8565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6109dc8061035d6000396000f3fe6080604052600436106100595760003560e01c80630e73684e146100b35780632e1a7d4d146100dc5780634120657a146101055780636f9fb98a1461014257806389d34e4b1461016d5780638da5cb5b14610198576100ae565b366100ae573373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040516100a491906105bb565b60405180910390a2005b600080fd5b3480156100bf57600080fd5b506100da60048036038101906100d59190610671565b6101c3565b005b3480156100e857600080fd5b5061010360048036038101906100fe91906106dd565b6103e9565b005b34801561011157600080fd5b5061012c6004803603810190610127919061070a565b610550565b6040516101399190610746565b60405180910390f35b34801561014e57600080fd5b50610157610570565b60405161016491906105bb565b60405180910390f35b34801561017957600080fd5b50610182610578565b60405161018f91906105bb565b60405180910390f35b3480156101a457600080fd5b506101ad61057e565b6040516101ba9190610770565b60405180910390f35b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610251576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102489061080e565b60405180910390fd5b8080156102a85750600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16155b156102ca57600260008154809291906102c09061085d565b9190505550610340565b801580156103215750600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff165b1561033f5760026000815480929190610339906108a5565b91905055505b5b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff167f14fcc60af8440546881d552eb01644aa747a1106cb912daf1a3b52e9932ed189826040516103dd9190610746565b60405180910390a25050565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16610475576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161046c9061091a565b60405180910390fd5b804710156104b8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104af90610986565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501580156104fe573d6000803e3d6000fd5b503373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b658260405161054591906105bb565b60405180910390a250565b60016020528060005260406000206000915054906101000a900460ff1681565b600047905090565b60025481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000819050919050565b6105b5816105a2565b82525050565b60006020820190506105d060008301846105ac565b92915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610606826105db565b9050919050565b610616816105fb565b811461062157600080fd5b50565b6000813590506106338161060d565b92915050565b60008115159050919050565b61064e81610639565b811461065957600080fd5b50565b60008135905061066b81610645565b92915050565b60008060408385031215610688576106876105d6565b5b600061069685828601610624565b92505060206106a78582860161065c565b9150509250929050565b6106ba816105a2565b81146106c557600080fd5b50565b6000813590506106d7816106b1565b92915050565b6000602082840312156106f3576106f26105d6565b5b6000610701848285016106c8565b91505092915050565b6000602082840312156107205761071f6105d6565b5b600061072e84828501610624565b91505092915050565b61074081610639565b82525050565b600060208201905061075b6000830184610737565b92915050565b61076a816105fb565b82525050565b60006020820190506107856000830184610761565b92915050565b600082825260208201905092915050565b7f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f60008201527f6e00000000000000000000000000000000000000000000000000000000000000602082015250565b60006107f860218361078b565b91506108038261079c565b604082019050919050565b60006020820190508181036000830152610827816107eb565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610868826105a2565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361089a5761089961082e565b5b600182019050919050565b60006108b0826105a2565b9150600082036108c3576108c261082e565b5b600182039050919050565b7f4e6f7420616c6c6f77656420746f207769746864726177000000000000000000600082015250565b600061090460178361078b565b915061090f826108ce565b602082019050919050565b60006020820190508181036000830152610933816108f7565b9050919050565b7f496e73756666696369656e742062616c616e6365000000000000000000000000600082015250565b600061097060148361078b565b915061097b8261093a565b602082019050919050565b6000602082019050818103600083015261099f81610963565b905091905056fea26469706673582212204c5c5bc49f588ae9c18b9af6ec048481d900a81336d2241578883700d803512164736f6c634300081a0033 From cc9d52928aec2cfa34b9850c9027c112ab5096cd Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Thu, 31 Oct 2024 18:32:12 -0700 Subject: [PATCH 13/19] Account and contract updating fully working. Contract Addresses all in place --- src/multichain_demo.html | 160 +++++++++++++++++++++++++++++++-------- 1 file changed, 127 insertions(+), 33 deletions(-) diff --git a/src/multichain_demo.html b/src/multichain_demo.html index 5a13d01b..928d6f8f 100644 --- a/src/multichain_demo.html +++ b/src/multichain_demo.html @@ -3,7 +3,7 @@ - D3.js Graph Visualization + Multichain API EVM Bridging Demo Dapp - @@ -84,681 +83,6 @@ - + diff --git a/src/multichain_demo.js b/src/multichain_demo.js new file mode 100644 index 00000000..ec5af9d1 --- /dev/null +++ b/src/multichain_demo.js @@ -0,0 +1,675 @@ +import * as d3 from "d3"; + +// +// Constants +// +const BridgeableScopes = { + // Sepolia + "eip155:11155111": { + name: "Sepolia", + contractAddress: '0x2e2512fd69cba059DFf557cD6f683a3279402e91', + blockExplorerUrl: 'https://sepolia.etherscan.io', + supports: ["eip155:59141", "eip155:421614", "eip155:11155420", "eip155:168587773"] + }, + // Linea Sepolia (no Arbitrium) + "eip155:59141": { + name: "Linea Sepolia", + contractAddress: '0x786Cb4C684F9D4bA4aBEbddE2c8a4D4ec80a9b78', + blockExplorerUrl: 'https://sepolia.lineascan.build/', + supports: ["eip155:11155111", "eip155:11155420", "eip155:168587773"] + }, + // Arbitrum Sepolia (no Linea) + "eip155:421614": { + name: "Arbitrum Sepolia", + contractAddress: '0x697230B7c217F4F45C460d5d181F792AB0aC6549', + blockExplorerUrl: 'https://sepolia.arbiscan.io/', + supports: ["eip155:11155111", "eip155:11155420", "eip155:168587773"] + }, + // OP Sepolia (no Blast) + "eip155:11155420": { + name: "OP Sepolia", + contractAddress: '0x697230B7c217F4F45C460d5d181F792AB0aC6549', + blockExplorerUrl: 'https://sepolia-optimism.etherscan.io/', + supports: ["eip155:11155111", "eip155:59141", "eip155:421614"] + }, + // Blast Sepolia (no OP) + "eip155:168587773": { + name: "Blast Sepolia", + contractAddress: '0x697230B7c217F4F45C460d5d181F792AB0aC6549', + blockExplorerUrl: 'https://sepolia.blastscan.io/', + supports: ["eip155:11155111", "eip155:59141", "eip155:421614"] + }, +} + +const pastelColors = [ + "#e8b6ae", "#f0c1ad", "#eed0ba", "#ebdec6", "#cdc0d6", + "#b8b1cf", "#959bad", "#97aeb8", "#a5bcc1", "#b2c9c9", + "#9ac7a8", "#b8dbc9", "#c5e3d2", "#d1ebdb", "#d6e1c8", + "#e5c28c", "#e8d097", "#edd9aa", "#f2e6bb", "#f9f3dd" +]; +const NODE_PADDING = 20; +const NODE_SCALE = 1.3; +const NODE_TEXT_START = "-2.4em" +const NODE_TEXT_SPACING = "1.2em" + +// +// State +// +let extensionPort; +let jsonRpcId; +let currentSessionScopes = {}; +let accounts = []; +let scopeStrings = []; + +let balances = {} + +let nodes = []; +let links = []; +for (let i = 0; i < nodes.length; i++) { + for (let j = i + 1; j < nodes.length; j++) { + links.push({ source: nodes[i].id, target: nodes[j].id }); + } +} + + +// +// Helpers +// + +// Dapp <-> Wallet Connection Initialization +async function connectExtension() { + const extensionId = document.getElementById("connectExtensionInput").value; + try { + extensionPort = chrome.runtime.connect(extensionId); // externally_connectable + extensionPort.onMessage.addListener((msg) => { + const { data: { method, params } } = msg; + // Subscription Events + if (method === "wallet_notify") { + handleEthSubscriptionDisplay( + params.scope, + params.notification.params.result.number, + ); + // Permission Events + } else if (method === "wallet_sessionChanged") { + onNewSessionScopes(params.sessionScopes); + } + console.log(msg.data); + }); + + // Dapp Initialization + checkWalletConnection(); + } catch (error) { + console.error(error); + alert("Failed to connect to extension!"); + } +} + +// Permission Handling +async function onNewSessionScopes(sessionScopes) { + const oldScopeStrings = scopeStrings; + const oldAccounts = accounts; + const eip155ScopeStrings = Object.keys(sessionScopes).filter((scopeString) => { + // return /eip155:[0-9]+/u.test(scopeString) + if (!BridgeableScopes[scopeString]) { + console.log(`ignoring ${scopeString} since it is not defined in BridgeableScopes`) + return false + } + return true + }); + + const eip155AccountsSet = new Set(); + + eip155ScopeStrings.forEach((scopeString) => { + + const scopeObject = sessionScopes[scopeString]; + + scopeObject.accounts.forEach((account) => { + const address = account.split(":")[2]; + eip155AccountsSet.add(address); + }); + }); + + currentSessionScopes = sessionScopes; + accounts = [...eip155AccountsSet]; + scopeStrings = [...eip155ScopeStrings]; + + + // updateConnectedAccountsDisplay(); + // updateScopeSelectOptions(); + // updateMethodButtonsState(); + + const accountsDidChange = oldAccounts.length !== accounts.length || accounts.some(account => !oldAccounts.includes(account)) + + oldScopeStrings.forEach((oldScopeString) => { + if (!scopeStrings.includes(oldScopeString)) { + removeNode(oldScopeString) + } + }); + scopeStrings.forEach(newScopeString => { + if (!oldScopeStrings.includes(newScopeString)) { + addNode(newScopeString) + } else if (accountsDidChange) { + updateNode(newScopeString, "black") + } + }) + + + startPolling() +} + +function getNodeLabelText(scopeString) { + const {name, contractAddress} = BridgeableScopes[scopeString]; + const accountsText = accounts.map(account => { + const balance = balances[scopeString]?.[account] || 0 + return `${account.slice(0,7)}...${account.slice(37,42)}: ${balance.toFixed(4)} ETH` + }).join('\n') + + const contractBalance = balances[scopeString]?.[contractAddress] || 0 + return`${name}\nBridge Contract: ${contractBalance.toFixed(4)} ETH\n--------------------------\n${accountsText}` +} + +// Permission Initialization +async function connectWallet() { + if (!extensionPort) { + alert("Connect to extension first."); + return; + } + try { + + const { sessionScopes } = await extensionPortRequest({ + method: "wallet_createSession", + params: { + eip155: { + references: ["11155111", "59141", "421614", "11155420", "168587773"], + methods: [ + "eth_getBalance", + "eth_sendTransaction", + "eth_subscribe", + ], + notifications: ["eth_subscription"], + }, + }, + }); + + await onNewSessionScopes(sessionScopes); + } catch (error) { + console.error(error); + alert("Failed to connect wallet!"); + } +} + +async function checkWalletConnection() { + try { + const { sessionScopes } = await extensionPortRequest({ + method: "wallet_getSession", + }); + await onNewSessionScopes(sessionScopes); + } catch (error) { + console.error("Failed to check wallet connection:", error); + alert("Failed to check wallet connection"); + } +} + +// +// externally_connectable helpers: +// normally this would be abstracted away +// by a helper library +// + +function generateJsonRpcId(opts) { + let max = Number.MAX_SAFE_INTEGER; + jsonRpcId = jsonRpcId ?? Math.floor(Math.random() * max); + + jsonRpcId = jsonRpcId % max; + jsonRpcId += 1; + return jsonRpcId; +} + +async function extensionPortRequest(request) { + const id = generateJsonRpcId(); + + extensionPort.postMessage({ + type: "caip-x", + data: { + jsonrpc: "2.0", + id, + ...request, + }, + }); + + return new Promise((resolve, reject) => { + const listener = (msg) => { + if (msg.type === "caip-x" && msg.data.id === id) { + const { result, error } = msg.data; + if (result) { + resolve(result); + } else { + reject(error); + } + extensionPort.onMessage.removeListener(listener); + } + }; + + extensionPort.onMessage.addListener(listener); + }); +} + +// +// D3 +// + +const svg = d3.select("body").append("svg") + .attr("width", window.innerWidth) + .attr("height", window.innerHeight); + +const simulation = d3.forceSimulation(nodes) + .force("link", d3.forceLink(links).id(d => d.id).distance(400)) // Further increased distance + .force("charge", d3.forceManyBody().strength(-1600)) // Further increased negative strength + .force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2)) + .force("collide", d3.forceCollide().radius(d => getRadius(d.label) * NODE_SCALE + NODE_PADDING + 40)); // Further increased radius + +let link = svg.append("g") + .attr("class", "links") + .selectAll("line") + .data(links) + .enter().append("line") + .attr("stroke-width", 2) + .attr("stroke", "#999") + .on("click", function(event, d) { + const sourceNode = nodes.find(node => node.id === d.source.id); + const targetNode = nodes.find(node => node.id === d.target.id); + showModal(`Source: ${sourceNode.label}\nTarget: ${targetNode.label}`); + }); + +let node = svg.append("g") + .attr("class", "nodes") + .selectAll("g") + .data(nodes) + .enter().append("g") + .on("click", function(event, d) { + showNodeModal(d.id); + }); + +node.append("circle") + .attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING) + .attr("fill", () => pastelColors[Math.floor(Math.random() * pastelColors.length)]); + +let text = node.append("text") + .attr("text-anchor", "middle") + .attr("dominant-baseline", "top"); // this may need to be added elsewhere + +text.selectAll("tspan") + .data(d => d.label.split('\n')) + .enter() + .append("tspan") + .attr("x", 0) + .attr("dy", (d, i) => i === 0 ? NODE_TEXT_START : NODE_TEXT_SPACING) + .text(d => d); + + +function updateGraph() { + d3.selectAll("g > *").remove() + + link = svg.append("g") + .attr("class", "links") + .selectAll("line") + .data(links) + .enter().append("line") + .attr("stroke-width", 2) + .attr("stroke", "#999") + .on("click", function(event, d) { + const sourceNode = nodes.find(node => node.id === d.source.id); + const targetNode = nodes.find(node => node.id === d.target.id); + showModal(`Source: ${sourceNode.label}\nTarget: ${targetNode.label}`); + }); + + node = svg.append("g") + .attr("class", "nodes") + .selectAll("g") + .data(nodes) + .enter().append("g") + .on("click", function(event, d) { + showNodeModal(d.id); + }); + + node.append("circle") + .attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING) + .attr("fill", () => pastelColors[Math.floor(Math.random() * pastelColors.length)]); + + text = node.append("text") + .attr("text-anchor", "middle") + .attr("dominant-baseline", "top"); // this may need to be added elsewhere + + text.selectAll("tspan") + .data(d => d.label.split('\n')) + .enter() + .append("tspan") + .attr("x", 0) + .attr("dy", (d, i) => i === 0 ? NODE_TEXT_START : NODE_TEXT_SPACING) + .text(d => d); +} + +simulation.on("tick", () => { + link + .attr("x1", d => d.source.x) + .attr("y1", d => d.source.y) + .attr("x2", d => d.target.x) + .attr("y2", d => d.target.y); + + node + .attr("transform", d => `translate(${d.x},${d.y})`); +}); + + +function getRadius(text) { + const context = document.createElement("canvas").getContext("2d"); + context.font = "10px sans-serif"; + const lines = text.split("\n"); + const maxWidth = Math.max(...lines.map(line => context.measureText(line).width)); + const lineHeight = 10; // Approximate line height + const textHeight = lines.length * lineHeight; + return Math.max(20, Math.sqrt(maxWidth * maxWidth + textHeight * textHeight) / 2 + 10); // Ensure a minimum radius of 20 +} + +window.addEventListener("resize", () => { + svg.attr("width", window.innerWidth) + .attr("height", window.innerHeight); + simulation.force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2)); + simulation.alpha(1).restart(); // Restart simulation to adjust positions +}); + +// Modal functionality +const modal = document.getElementById("myModal"); +const modalText = document.getElementById("modal-text"); +const span = document.getElementsByClassName("close")[0]; + +function showModal(content) { + modalText.textContent = content; + modal.style.display = "block"; +} + +function showNodeModal(id) { + const {name, contractAddress, supports, blockExplorerUrl} = BridgeableScopes[id] + modalText.innerHTML = ` +

${name}

+

Block Explorer

+

Bridge Contract: ${contractAddress} (${(balances[id]?.[contractAddress] || 0).toFixed(4)} ETH)

+

Bridgeable Scopes: ${supports.map(scopeString => BridgeableScopes[scopeString].name).join(', ')}

+
+ Accounts: +
+
    + ${accounts.map(account => { + const balance = balances[id]?.[account] || 0 + return `
  • ${account} (${balance.toFixed(4)} ETH)
  • ` + }).join('')} +
+ + `; + modal.style.display = "block"; +} + +span.onclick = function() { + modal.style.display = "none"; +} + +window.onclick = function(event) { + if (event.target == modal) { + modal.style.display = "none"; + } +} + +// setTimeout(() => { +// nodes[0].label = generateLabel(); +// text.selectAll("tspan").remove(); +// text.selectAll("tspan") +// .data(d => d.label.split('\n')) +// .enter() +// .append("tspan") +// .attr("x", 0) +// .attr("dy", (d, i) => i === 0 ? NODE_TEXT_START : NODE_TEXT_SPACING) +// .text(d => d); +// node.select("circle").attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING); +// simulation.alpha(1).restart(); // Restart simulation to adjust positions +// }, 3000); + +// Flash an edge +// setTimeout(() => { +// const randomLink = d3.select(link.nodes()[Math.floor(Math.random() * links.length)]); +// const originalColor = randomLink.attr("stroke"); +// randomLink.attr("stroke", "green"); +// setTimeout(() => { +// randomLink.attr("stroke", originalColor); +// }, 3000); +// }, 5000); + + +// setTimeout(() => { +// const randomLink = links[Math.floor(Math.random() * links.length)]; +// const linkSelection = d3.select(link.nodes()[links.indexOf(randomLink)]); +// const sourceNode = nodes.find(node => node.id === randomLink.source.id); +// const targetNode = nodes.find(node => node.id === randomLink.target.id); + +// const edgeLabel = svg.append("text") +// .attr("x", (sourceNode.x + targetNode.x) / 2) +// .attr("y", (sourceNode.y + targetNode.y) / 2) +// .attr("dy", -5) +// .attr("text-anchor", "middle") +// .attr("fill", "green") +// .text("sending..."); + +// setTimeout(() => { +// edgeLabel.remove(); +// }, 3000); +// }, 5000); + + +function addNode(id) { + console.log('adding node', id) + const newNode = { id, label: getNodeLabelText(id) }; + nodes.push(newNode); + + const supportedScopes = BridgeableScopes[id]?.supports || [] + + supportedScopes.forEach((supportedScope) => { + const targetNode = nodes.find(node => node.id === supportedScope) + const targetLink = links.find(link => + (link.source.id === id && link.target.id === supportedScope) || + (link.source.id === supportedScope && link.target.id === id) + ) + if (targetNode && !targetLink) { + links.push({ source: id, target: targetNode.id }); + } + }) + + // Update the simulation with the new node and link + simulation.nodes(nodes); + simulation.force("link").links(links); + + // Add the new node to the SVG + // const newNodeSelection = svg.select(".nodes") + // .selectAll("g") + // .data(nodes) + // .enter() + // .append("g") + // .on("click", function(event, d) { + // showModal(d.label); + // }); + + // newNodeSelection.append("circle") + // .attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING) + // .attr("fill", () => pastelColors[Math.floor(Math.random() * pastelColors.length)]); + + // const newText = newNodeSelection.append("text") + // .attr("text-anchor", "middle"); + + // newText.selectAll("tspan") + // .data(d => d.label.split('\n')) + // .enter() + // .append("tspan") + // .attr("x", 0) + // .attr("dy", (d, i) => i === 0 ? NODE_TEXT_START : NODE_TEXT_SPACING) + // .text(d => d); + + + // Restart the simulation + updateGraph() + simulation.alpha(1).restart(); +} + +function removeNode(id) { + console.log('removing node', id) + nodes = nodes.filter(node => node.id !== id); + links = links.filter(link => link.source.id !== id && link.target.id !== id); + + // Update the simulation with the removed node and links + simulation.nodes(nodes); + simulation.force("link").links(links); + + // Remove the node and its edges from the SVG + svg.select(".nodes").selectAll("g") + .data(nodes, d => d.id) + .exit().remove(); + + svg.select(".links").selectAll("line") + .data(links, d => `${d.source.id}-${d.target.id}`) + .exit().remove(); + + updateGraph() + // Restart the simulation + simulation.alpha(1).restart(); +} + +function updateNode(id, color) { + const targetNode = nodes.find(node => node.id === id) + targetNode.label = getNodeLabelText(id) + text.selectAll("tspan").remove(); + text.selectAll("tspan") + .data(d => d.label.split('\n')) + .enter() + .append("tspan") + .attr("x", 0) + .attr("dy", (d, i) => i === 0 ? NODE_TEXT_START : NODE_TEXT_SPACING) + .text(d => d); + node.select("circle").attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING); + + if (color) { + const targetNodeCircle = d3.select(node.nodes()[nodes.indexOf(targetNode)]).select("circle"); + targetNodeCircle.attr("stroke", color).attr("stroke-width", 3); + setTimeout(() => { + targetNodeCircle.attr("stroke", null).attr("stroke-width", null); + }, 2000); + } + // Restart simulation to adjust positions + simulation.alpha(1).restart(); +} + +// DOM +document.getElementById("connectExtensionButton").addEventListener("click", connectExtension); +document.getElementById("connectButton").addEventListener("click", connectWallet); + +// Loops +async function updateAccountBalances() { + if(!extensionPort) { + return + } + if(!accounts.length || !scopeStrings.length) { + return + } + + for (let account of accounts) { + // Notice how we hit multiple chains at once + await Promise.allSettled(scopeStrings.map(async scopeString => { + // console.log(`updating balance for account ${account} on scope ${scopeString}`) + try { + const balance = await extensionPortRequest({ + method: "wallet_invokeMethod", + params: { + scope: scopeString, + request: { + "method": "eth_getBalance", + "params": [ + account, + "latest" + ], + }, + }, + }) + balances[scopeString] = balances[scopeString] || {} + const oldBalance = balances[scopeString][account] || 0 + balances[scopeString][account] = parseInt(balance, 16) / Math.pow(10, 18); + + if (oldBalance !== balances[scopeString][account]) { + // console.log(`updating node for account ${account} on scope ${scopeString} with ${balances[scopeString][account]}`) + updateNode(scopeString, !oldBalance || balances[scopeString][account] > oldBalance ? "green" : "red") + } + + } catch (error) { + console.error(`failed updating balance for account ${account} on scope ${scopeString}`, error) + } + + })) + } +} +async function updateAccountBalancesPoll() { + await updateAccountBalances() + setTimeout(() => { + updateAccountBalancesPoll() + }, 12000) // Mainnet block time, idk +} + +async function updateContractBalances() { + if(!extensionPort) { + return + } + if(!accounts.length || !scopeStrings.length) { + return + } + + + await Promise.allSettled(scopeStrings.map(async scopeString => { + const {contractAddress} = BridgeableScopes[scopeString] + // console.log(`updating balance for contract ${contractAddress} on scope ${scopeString}`) + try { + const balance = await extensionPortRequest({ + method: "wallet_invokeMethod", + params: { + scope: scopeString, + request: { + "method": "eth_getBalance", + "params": [ + contractAddress, + "latest" + ], + }, + }, + }) + balances[scopeString] = balances[scopeString] || {} + const oldBalance = balances[scopeString][contractAddress] || 0 + balances[scopeString][contractAddress] = parseInt(balance, 16) / Math.pow(10, 18); + + if (oldBalance !== balances[scopeString][contractAddress]) { + // console.log(`updating node for contract ${contractAddress} on scope ${scopeString} with ${balances[scopeString][contractAddress]}`) + updateNode(scopeString) + } + + } catch (error) { + console.error(`failed updating balance for contract ${contractAddress} on scope ${scopeString}`, error) + } + })) +} +async function updateContractBalancesPoll() { + await updateContractBalances() + setTimeout(() => { + updateContractBalancesPoll() + }, 12000) // Mainnet block time, idk +} + +let isPolling; +async function startPolling() { + if (!isPolling) { + isPolling = true + updateAccountBalancesPoll() + updateContractBalancesPoll() + } +} diff --git a/webpack.config.js b/webpack.config.js index b6bfe381..7f3da68c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -20,6 +20,7 @@ module.exports = { devtool: 'eval-source-map', mode: 'development', entry: { + multichain_demo: './src/multichain_demo.js', main: './src/index.js', request: './src/request.js', }, diff --git a/yarn.lock b/yarn.lock index 853e8563..eebac236 100644 --- a/yarn.lock +++ b/yarn.lock @@ -110,6 +110,11 @@ resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== +"@ethereumjs/rlp@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-5.0.2.tgz#c89bd82f2f3bec248ab2d517ae25f5bbc4aac842" + integrity sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA== + "@ethereumjs/tx@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-4.2.0.tgz#5988ae15daf5a3b3c815493bc6b495e76009e853" @@ -1303,6 +1308,13 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== +"@types/ws@8.5.3": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== + dependencies: + "@types/node" "*" + "@types/ws@^7.4.4": version "7.4.7" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" @@ -1904,6 +1916,11 @@ abbrev@^1.0.0: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abitype@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.7.1.tgz#16db20abe67de80f6183cf75f3de1ff86453b745" + integrity sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -2547,6 +2564,11 @@ colorette@^2.0.10, colorette@^2.0.14: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== +commander@7: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + commander@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" @@ -2641,7 +2663,7 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -crc-32@^1.2.0: +crc-32@^1.2.0, crc-32@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== @@ -2697,6 +2719,250 @@ csstype@^3.1.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== +"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + +d3-axis@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" + integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== + +d3-brush@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" + integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "3" + d3-transition "3" + +d3-chord@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" + integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== + dependencies: + d3-path "1 - 3" + +"d3-color@1 - 3", d3-color@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-contour@4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.2.tgz#bb92063bc8c5663acb2422f99c73cbb6c6ae3bcc" + integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA== + dependencies: + d3-array "^3.2.0" + +d3-delaunay@6: + version "6.0.4" + resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b" + integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== + dependencies: + delaunator "5" + +"d3-dispatch@1 - 3", d3-dispatch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== + +"d3-drag@2 - 3", d3-drag@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== + dependencies: + d3-dispatch "1 - 3" + d3-selection "3" + +"d3-dsv@1 - 3", d3-dsv@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" + integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== + dependencies: + commander "7" + iconv-lite "0.6" + rw "1" + +"d3-ease@1 - 3", d3-ease@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +d3-fetch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" + integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== + dependencies: + d3-dsv "1 - 3" + +d3-force@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" + integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== + dependencies: + d3-dispatch "1 - 3" + d3-quadtree "1 - 3" + d3-timer "1 - 3" + +"d3-format@1 - 3", d3-format@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + +d3-geo@3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.1.tgz#6027cf51246f9b2ebd64f99e01dc7c3364033a4d" + integrity sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q== + dependencies: + d3-array "2.5.0 - 3" + +d3-hierarchy@3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" + integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== + +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + +d3-polygon@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" + integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== + +"d3-quadtree@1 - 3", d3-quadtree@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" + integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== + +d3-random@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" + integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== + +d3-scale-chromatic@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#34c39da298b23c20e02f1a4b239bd0f22e7f1314" + integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ== + dependencies: + d3-color "1 - 3" + d3-interpolate "1 - 3" + +d3-scale@4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +"d3-selection@2 - 3", d3-selection@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== + +d3-shape@3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + +"d3-time-format@2 - 4", d3-time-format@4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + +"d3-timer@1 - 3", d3-timer@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + +"d3-transition@2 - 3", d3-transition@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" + +d3-zoom@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" + +d3@^7.9.0: + version "7.9.0" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.9.0.tgz#579e7acb3d749caf8860bd1741ae8d371070cd5d" + integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA== + dependencies: + d3-array "3" + d3-axis "3" + d3-brush "3" + d3-chord "3" + d3-color "3" + d3-contour "4" + d3-delaunay "6" + d3-dispatch "3" + d3-drag "3" + d3-dsv "3" + d3-ease "3" + d3-fetch "3" + d3-force "3" + d3-format "3" + d3-geo "3" + d3-hierarchy "3" + d3-interpolate "3" + d3-path "3" + d3-polygon "3" + d3-quadtree "3" + d3-random "3" + d3-scale "4" + d3-scale-chromatic "3" + d3-selection "3" + d3-shape "3" + d3-time "3" + d3-time-format "4" + d3-timer "3" + d3-transition "3" + d3-zoom "3" + date-fns@^2.29.3: version "2.30.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" @@ -2783,6 +3049,13 @@ del@^4.1.1: pify "^4.0.1" rimraf "^2.6.3" +delaunator@5: + version "5.0.1" + resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.1.tgz#39032b08053923e924d6094fe2cde1a99cc51278" + integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw== + dependencies: + robust-predicates "^3.0.2" + delay@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" @@ -4092,7 +4365,7 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2: +iconv-lite@0.6, iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -4158,6 +4431,11 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + interpret@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" @@ -4386,6 +4664,11 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== +isomorphic-ws@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" + integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== + jackspeak@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" @@ -5763,6 +6046,11 @@ rlp@^2.0.0: dependencies: bn.js "^4.11.1" +robust-predicates@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" + integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== + rpc-websockets@^9.0.2: version "9.0.4" resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.0.4.tgz#9d8ee82533b5d1e13d9ded729e3e38d0d8fa083f" @@ -5784,6 +6072,11 @@ run-parallel@^1.1.9: resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== +rw@1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== + rxjs@^6.6.3: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" @@ -5956,6 +6249,11 @@ set-function-length@^1.2.1: gopd "^1.0.1" has-property-descriptors "^1.0.2" +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + setprototypeof@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" @@ -6655,6 +6953,171 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" +web3-core@^4.4.0, web3-core@^4.5.1, web3-core@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.7.0.tgz#a109ed079e5f98a968487f4bb18c7aaf845f768a" + integrity sha512-skP4P56fhlrE+rIuS4WY9fTdja1DPml2xrrDmv+vQhPtmSFBs7CqesycIRLQh4dK1D4de/a23tkX6DLYdUt3nA== + dependencies: + web3-errors "^1.3.0" + web3-eth-accounts "^4.2.1" + web3-eth-iban "^4.0.7" + web3-providers-http "^4.2.0" + web3-providers-ws "^4.0.8" + web3-types "^1.8.1" + web3-utils "^4.3.2" + web3-validator "^2.0.6" + optionalDependencies: + web3-providers-ipc "^4.0.7" + +web3-errors@^1.1.3, web3-errors@^1.2.0, web3-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.3.0.tgz#504e4d3218899df108856940087a8022d6688d74" + integrity sha512-j5JkAKCtuVMbY3F5PYXBqg1vWrtF4jcyyMY1rlw8a4PV67AkqlepjGgpzWJZd56Mt+TvHy6DA1F/3Id8LatDSQ== + dependencies: + web3-types "^1.7.0" + +web3-eth-abi@^4.2.3, web3-eth-abi@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.3.0.tgz#9036541206418b9db6f1db295ad87a6f0fa0ed7d" + integrity sha512-OqZPGGxHmfKJt33BfpclEMmWvnnLJ/B+jVTnVagd2OIU1kIv09xf/E60ei0eGeG612uFy/pPq31u4RidF/gf6g== + dependencies: + abitype "0.7.1" + web3-errors "^1.3.0" + web3-types "^1.8.1" + web3-utils "^4.3.2" + web3-validator "^2.0.6" + +web3-eth-accounts@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.2.1.tgz#db27399137e1a26f9d467b9500019a70771f5724" + integrity sha512-aOlEZFzqAgKprKs7+DGArU4r9b+ILBjThpeq42aY7LAQcP+mSpsWcQgbIRK3r/n3OwTYZ3aLPk0Ih70O/LwnYA== + dependencies: + "@ethereumjs/rlp" "^4.0.1" + crc-32 "^1.2.2" + ethereum-cryptography "^2.0.0" + web3-errors "^1.3.0" + web3-types "^1.7.0" + web3-utils "^4.3.1" + web3-validator "^2.0.6" + +web3-eth-contract@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.7.0.tgz#119a744e8a35f60fd7bc3e4f8637f0380a3d0e85" + integrity sha512-fdStoBOjFyMHwlyJmSUt/BTDL1ATwKGmG3zDXQ/zTKlkkW/F/074ut0Vry4GuwSBg9acMHc0ycOiZx9ZKjNHsw== + dependencies: + "@ethereumjs/rlp" "^5.0.2" + web3-core "^4.5.1" + web3-errors "^1.3.0" + web3-eth "^4.8.2" + web3-eth-abi "^4.2.3" + web3-types "^1.7.0" + web3-utils "^4.3.1" + web3-validator "^2.0.6" + +web3-eth-iban@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-4.0.7.tgz#ee504f845d7b6315f0be78fcf070ccd5d38e4aaf" + integrity sha512-8weKLa9KuKRzibC87vNLdkinpUE30gn0IGY027F8doeJdcPUfsa4IlBgNC4k4HLBembBB2CTU0Kr/HAOqMeYVQ== + dependencies: + web3-errors "^1.1.3" + web3-types "^1.3.0" + web3-utils "^4.0.7" + web3-validator "^2.0.3" + +web3-eth@^4.8.2: + version "4.10.0" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.10.0.tgz#a62017908e543fe1bae398e2384bd29d1ebbbacd" + integrity sha512-8d7epCOm1hv/xGnOW8pWNkO5Ze9b+LKl81Pa1VUdRi2xZKtBaQsk+4qg6EnqeDF6SPpL502wNmX6TAB69vGBWw== + dependencies: + setimmediate "^1.0.5" + web3-core "^4.7.0" + web3-errors "^1.3.0" + web3-eth-abi "^4.3.0" + web3-eth-accounts "^4.2.1" + web3-net "^4.1.0" + web3-providers-ws "^4.0.8" + web3-rpc-methods "^1.3.0" + web3-types "^1.8.1" + web3-utils "^4.3.2" + web3-validator "^2.0.6" + +web3-net@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-4.1.0.tgz#db7bde675e58b153339e4f149f29ec0410d6bab2" + integrity sha512-WWmfvHVIXWEoBDWdgKNYKN8rAy6SgluZ0abyRyXOL3ESr7ym7pKWbfP4fjApIHlYTh8tNqkrdPfM4Dyi6CA0SA== + dependencies: + web3-core "^4.4.0" + web3-rpc-methods "^1.3.0" + web3-types "^1.6.0" + web3-utils "^4.3.0" + +web3-providers-http@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-4.2.0.tgz#0f4bf424681a068d49994aa7fabc69bed45ac50b" + integrity sha512-IPMnDtHB7dVwaB7/mMxAZzyq7d5ezfO1+Vw0bNfAeIi7gaDlJiggp85SdyAfOgov8AMUA/dyiY72kQ0KmjXKvQ== + dependencies: + cross-fetch "^4.0.0" + web3-errors "^1.3.0" + web3-types "^1.7.0" + web3-utils "^4.3.1" + +web3-providers-ipc@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-4.0.7.tgz#9ec4c8565053af005a5170ba80cddeb40ff3e3d3" + integrity sha512-YbNqY4zUvIaK2MHr1lQFE53/8t/ejHtJchrWn9zVbFMGXlTsOAbNoIoZWROrg1v+hCBvT2c9z8xt7e/+uz5p1g== + dependencies: + web3-errors "^1.1.3" + web3-types "^1.3.0" + web3-utils "^4.0.7" + +web3-providers-ws@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-4.0.8.tgz#6de7b262f7ec6df1a2dff466ba91d7ebdac2c45e" + integrity sha512-goJdgata7v4pyzHRsg9fSegUG4gVnHZSHODhNnn6J93ykHkBI1nz4fjlGpcQLUMi4jAMz6SHl9Ibzs2jj9xqPw== + dependencies: + "@types/ws" "8.5.3" + isomorphic-ws "^5.0.0" + web3-errors "^1.2.0" + web3-types "^1.7.0" + web3-utils "^4.3.1" + ws "^8.17.1" + +web3-rpc-methods@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.3.0.tgz#d5ee299a69389d63822d354ddee2c6a121a6f670" + integrity sha512-/CHmzGN+IYgdBOme7PdqzF+FNeMleefzqs0LVOduncSaqsppeOEoskLXb2anSpzmQAP3xZJPaTrkQPWSJMORig== + dependencies: + web3-core "^4.4.0" + web3-types "^1.6.0" + web3-validator "^2.0.6" + +web3-types@^1.3.0, web3-types@^1.6.0, web3-types@^1.7.0, web3-types@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.8.1.tgz#6379aca0f99330eb0f8f6ca80a3de93129b58339" + integrity sha512-isspsvQbBJFUkJYz2Badb7dz/BrLLLpOop/WmnL5InyYMr7kYYc8038NYO7Vkp1M7Bupa/wg+yALvBm7EGbyoQ== + +web3-utils@^4.0.7, web3-utils@^4.3.0, web3-utils@^4.3.1, web3-utils@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.3.2.tgz#a952428d677b635fd0c16044ae4c534807a39639" + integrity sha512-bEFpYEBMf6ER78Uvj2mdsCbaLGLK9kABOsa3TtXOEEhuaMy/RK0KlRkKoZ2vmf/p3hB8e1q5erknZ6Hy7AVp7A== + dependencies: + ethereum-cryptography "^2.0.0" + eventemitter3 "^5.0.1" + web3-errors "^1.3.0" + web3-types "^1.8.1" + web3-validator "^2.0.6" + +web3-validator@^2.0.3, web3-validator@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.6.tgz#a0cdaa39e1d1708ece5fae155b034e29d6a19248" + integrity sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg== + dependencies: + ethereum-cryptography "^2.0.0" + util "^0.12.5" + web3-errors "^1.2.0" + web3-types "^1.6.0" + zod "^3.21.4" + "webextension-polyfill@>=0.10.0 <1.0": version "0.11.0" resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.11.0.tgz#1640c0d27192424fd5b420237acbe453f88c8246" @@ -6931,6 +7394,11 @@ ws@^8.13.0, ws@^8.5.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== +ws@^8.17.1: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + ws@~8.11.0: version "8.11.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" @@ -7021,3 +7489,8 @@ yargs@^17.0.1: string-width "^4.2.3" y18n "^5.0.5" yargs-parser "^21.0.0" + +zod@^3.21.4: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== From 63a9b233ec3282cf5fdb886762eeaa388a2a5c51 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Fri, 1 Nov 2024 08:23:03 -0700 Subject: [PATCH 15/19] add subscription and staggering --- src/multichain_demo.js | 183 ++++++++++++++++++++++++++++++++--------- 1 file changed, 146 insertions(+), 37 deletions(-) diff --git a/src/multichain_demo.js b/src/multichain_demo.js index ec5af9d1..d3b68cb5 100644 --- a/src/multichain_demo.js +++ b/src/multichain_demo.js @@ -49,8 +49,16 @@ const pastelColors = [ ]; const NODE_PADDING = 20; const NODE_SCALE = 1.3; -const NODE_TEXT_START = "-2.4em" -const NODE_TEXT_SPACING = "1.2em" +const NODE_TEXT_START = "-2.4em"; +const NODE_TEXT_SPACING = "1.2em"; + +const USE_SUBSCRIPTIONS = true; // use false for polling +// subscriptions +const SUBSCRIPTION_STAGGER = 10000; +const SUBSCRIPTION_DEBOUNCE = 2000; +const SUBSCRIPTION_REQUEST_DELAY = 666; // just to avoid rate limiting a bit better +// polling +const POLLING_RATE = 12500; // mainnet block time -ish // // State @@ -61,16 +69,11 @@ let currentSessionScopes = {}; let accounts = []; let scopeStrings = []; -let balances = {} +let balances = {}; +let subscriptionDebounce = {}; let nodes = []; let links = []; -for (let i = 0; i < nodes.length; i++) { - for (let j = i + 1; j < nodes.length; j++) { - links.push({ source: nodes[i].id, target: nodes[j].id }); - } -} - // // Helpers @@ -85,7 +88,7 @@ async function connectExtension() { const { data: { method, params } } = msg; // Subscription Events if (method === "wallet_notify") { - handleEthSubscriptionDisplay( + handleEthSubscription( params.scope, params.notification.params.result.number, ); @@ -93,7 +96,7 @@ async function connectExtension() { } else if (method === "wallet_sessionChanged") { onNewSessionScopes(params.sessionScopes); } - console.log(msg.data); + // console.log(msg.data); }); // Dapp Initialization @@ -111,7 +114,7 @@ async function onNewSessionScopes(sessionScopes) { const eip155ScopeStrings = Object.keys(sessionScopes).filter((scopeString) => { // return /eip155:[0-9]+/u.test(scopeString) if (!BridgeableScopes[scopeString]) { - console.log(`ignoring ${scopeString} since it is not defined in BridgeableScopes`) + // console.log(`ignoring ${scopeString} since it is not defined in BridgeableScopes`) return false } return true @@ -133,11 +136,6 @@ async function onNewSessionScopes(sessionScopes) { accounts = [...eip155AccountsSet]; scopeStrings = [...eip155ScopeStrings]; - - // updateConnectedAccountsDisplay(); - // updateScopeSelectOptions(); - // updateMethodButtonsState(); - const accountsDidChange = oldAccounts.length !== accounts.length || accounts.some(account => !oldAccounts.includes(account)) oldScopeStrings.forEach((oldScopeString) => { @@ -148,13 +146,37 @@ async function onNewSessionScopes(sessionScopes) { scopeStrings.forEach(newScopeString => { if (!oldScopeStrings.includes(newScopeString)) { addNode(newScopeString) + if (USE_SUBSCRIPTIONS) { + setTimeout(() => { + subscribeToBlockHeaders(newScopeString) + }, Math.floor(Math.random() * (SUBSCRIPTION_STAGGER + 1))) + } } else if (accountsDidChange) { updateNode(newScopeString, "black") } }) + if (!USE_SUBSCRIPTIONS) { + startPolling() + } +} - startPolling() +async function subscribeToBlockHeaders(scopeString) { + try { + await extensionPortRequest({ + method: "wallet_invokeMethod", + params: { + scope: scopeString, + request: { + method: "eth_subscribe", + params: ["newHeads"], + }, + }, + }); + } catch (error) { + console.error(error); + alert("Subscription failed!"); + } } function getNodeLabelText(scopeString) { @@ -419,20 +441,6 @@ window.onclick = function(event) { } } -// setTimeout(() => { -// nodes[0].label = generateLabel(); -// text.selectAll("tspan").remove(); -// text.selectAll("tspan") -// .data(d => d.label.split('\n')) -// .enter() -// .append("tspan") -// .attr("x", 0) -// .attr("dy", (d, i) => i === 0 ? NODE_TEXT_START : NODE_TEXT_SPACING) -// .text(d => d); -// node.select("circle").attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING); -// simulation.alpha(1).restart(); // Restart simulation to adjust positions -// }, 3000); - // Flash an edge // setTimeout(() => { // const randomLink = d3.select(link.nodes()[Math.floor(Math.random() * links.length)]); @@ -465,7 +473,7 @@ window.onclick = function(event) { function addNode(id) { - console.log('adding node', id) + // console.log('adding node', id) const newNode = { id, label: getNodeLabelText(id) }; nodes.push(newNode); @@ -518,7 +526,7 @@ function addNode(id) { } function removeNode(id) { - console.log('removing node', id) + // console.log('removing node', id) nodes = nodes.filter(node => node.id !== id); links = links.filter(link => link.source.id !== id && link.target.id !== id); @@ -568,7 +576,67 @@ function updateNode(id, color) { document.getElementById("connectExtensionButton").addEventListener("click", connectExtension); document.getElementById("connectButton").addEventListener("click", connectWallet); -// Loops + +// Events/Loops +async function handleEthSubscription(scopeString, _blockHead) { + if (!scopeStrings.includes(scopeString)) { + // Not sure how this would happen + return + } + if(subscriptionDebounce[scopeString]) { + return + } + subscriptionDebounce[scopeString] = true + + console.log(`Subscription: updating account balances and contract balance for scope ${scopeString}`) + await updateAccountBalancesForScope(scopeString) + await updateContractBalanceForScope(scopeString) + + setTimeout(() => { + subscriptionDebounce[scopeString] = false + }, SUBSCRIPTION_DEBOUNCE) +} + +async function updateAccountBalancesForScope(scopeString) { + if(!extensionPort) { + return + } + if(!accounts.length || !scopeStrings.includes(scopeString)) { + return + } + + for (let account of accounts) { + // console.log(`updating balance for account ${account} on scope ${scopeString}`) + try { + const balance = await extensionPortRequest({ + method: "wallet_invokeMethod", + params: { + scope: scopeString, + request: { + "method": "eth_getBalance", + "params": [ + account, + "latest" + ], + }, + }, + }) + balances[scopeString] = balances[scopeString] || {} + const oldBalance = balances[scopeString][account] || 0 + balances[scopeString][account] = parseInt(balance, 16) / Math.pow(10, 18); + + if (oldBalance !== balances[scopeString][account]) { + // console.log(`updating node for account ${account} on scope ${scopeString} with ${balances[scopeString][account]}`) + updateNode(scopeString, !oldBalance || balances[scopeString][account] > oldBalance ? "green" : "red") + } + + } catch (error) { + console.error(`failed updating balance for account ${account} on scope ${scopeString}`, error) + } + await new Promise((resolve) => setTimeout(resolve, SUBSCRIPTION_REQUEST_DELAY)) + } +} + async function updateAccountBalances() { if(!extensionPort) { return @@ -578,6 +646,7 @@ async function updateAccountBalances() { } for (let account of accounts) { + console.log(`Polling: updating account balances for account ${account}`) // Notice how we hit multiple chains at once await Promise.allSettled(scopeStrings.map(async scopeString => { // console.log(`updating balance for account ${account} on scope ${scopeString}`) @@ -615,7 +684,46 @@ async function updateAccountBalancesPoll() { await updateAccountBalances() setTimeout(() => { updateAccountBalancesPoll() - }, 12000) // Mainnet block time, idk + }, POLLING_RATE) +} + + +async function updateContractBalanceForScope(scopeString) { + if(!extensionPort) { + return + } + if(!accounts.length || !scopeStrings.includes(scopeString)) { + return + } + + const {contractAddress} = BridgeableScopes[scopeString] + // console.log(`updating balance for contract ${contractAddress} on scope ${scopeString}`) + try { + const balance = await extensionPortRequest({ + method: "wallet_invokeMethod", + params: { + scope: scopeString, + request: { + "method": "eth_getBalance", + "params": [ + contractAddress, + "latest" + ], + }, + }, + }) + balances[scopeString] = balances[scopeString] || {} + const oldBalance = balances[scopeString][contractAddress] || 0 + balances[scopeString][contractAddress] = parseInt(balance, 16) / Math.pow(10, 18); + + if (oldBalance !== balances[scopeString][contractAddress]) { + // console.log(`updating node for contract ${contractAddress} on scope ${scopeString} with ${balances[scopeString][contractAddress]}`) + updateNode(scopeString) + } + + } catch (error) { + console.error(`failed updating balance for contract ${contractAddress} on scope ${scopeString}`, error) + } } async function updateContractBalances() { @@ -659,10 +767,11 @@ async function updateContractBalances() { })) } async function updateContractBalancesPoll() { + console.log("Polling: updating all contract balances") await updateContractBalances() setTimeout(() => { updateContractBalancesPoll() - }, 12000) // Mainnet block time, idk + }, POLLING_RATE) } let isPolling; From 4e0e504a278df93a0380e1eaaab7ca5268d5fdbd Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Fri, 1 Nov 2024 08:27:32 -0700 Subject: [PATCH 16/19] make node modal addresses link to block explorer --- src/multichain_demo.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/multichain_demo.js b/src/multichain_demo.js index d3b68cb5..8b54693d 100644 --- a/src/multichain_demo.js +++ b/src/multichain_demo.js @@ -410,12 +410,17 @@ function showModal(content) { modal.style.display = "block"; } + +function generateBlockExplorerAddressLink(blockExplorerUrl, address, label) { + return `${label || address}` +} + function showNodeModal(id) { const {name, contractAddress, supports, blockExplorerUrl} = BridgeableScopes[id] modalText.innerHTML = `

${name}

Block Explorer

-

Bridge Contract: ${contractAddress} (${(balances[id]?.[contractAddress] || 0).toFixed(4)} ETH)

+

Bridge Contract: ${generateBlockExplorerAddressLink(blockExplorerUrl, contractAddress)} (${(balances[id]?.[contractAddress] || 0).toFixed(4)} ETH)

Bridgeable Scopes: ${supports.map(scopeString => BridgeableScopes[scopeString].name).join(', ')}


Accounts: @@ -423,7 +428,7 @@ function showNodeModal(id) {
    ${accounts.map(account => { const balance = balances[id]?.[account] || 0 - return `
  • ${account} (${balance.toFixed(4)} ETH)
  • ` + return `
  • ${generateBlockExplorerAddressLink(blockExplorerUrl, account)} (${balance.toFixed(4)} ETH)
  • ` }).join('')}
From e8efba916c82be0a4ce5ce0623c2f0f3e7626801 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Fri, 1 Nov 2024 09:50:41 -0700 Subject: [PATCH 17/19] Add edge modal --- src/multichain_demo.js | 139 +++++++++++++++++++++++++++++++++++------ 1 file changed, 120 insertions(+), 19 deletions(-) diff --git a/src/multichain_demo.js b/src/multichain_demo.js index 8b54693d..5d1a15a5 100644 --- a/src/multichain_demo.js +++ b/src/multichain_demo.js @@ -51,11 +51,14 @@ const NODE_PADDING = 20; const NODE_SCALE = 1.3; const NODE_TEXT_START = "-2.4em"; const NODE_TEXT_SPACING = "1.2em"; +const EDGE_STROKE_WIDTH = 3; + +const ETH_VALUE_PRECISION = 4; const USE_SUBSCRIPTIONS = true; // use false for polling // subscriptions -const SUBSCRIPTION_STAGGER = 10000; -const SUBSCRIPTION_DEBOUNCE = 2000; +const SUBSCRIPTION_STAGGER = 5000; +const SUBSCRIPTION_DEBOUNCE = 2500; const SUBSCRIPTION_REQUEST_DELAY = 666; // just to avoid rate limiting a bit better // polling const POLLING_RATE = 12500; // mainnet block time -ish @@ -179,15 +182,19 @@ async function subscribeToBlockHeaders(scopeString) { } } +function getShortAddress(address) { + return `${address.slice(0,7)}...${address.slice(37,42)}` +} + function getNodeLabelText(scopeString) { const {name, contractAddress} = BridgeableScopes[scopeString]; const accountsText = accounts.map(account => { const balance = balances[scopeString]?.[account] || 0 - return `${account.slice(0,7)}...${account.slice(37,42)}: ${balance.toFixed(4)} ETH` + return `${getShortAddress(account)}: ${balance.toFixed(ETH_VALUE_PRECISION)} ETH` }).join('\n') const contractBalance = balances[scopeString]?.[contractAddress] || 0 - return`${name}\nBridge Contract: ${contractBalance.toFixed(4)} ETH\n--------------------------\n${accountsText}` + return`${name}\nBridge Contract: ${contractBalance.toFixed(ETH_VALUE_PRECISION)} ETH\n--------------------------\n${accountsText}` } // Permission Initialization @@ -295,12 +302,10 @@ let link = svg.append("g") .selectAll("line") .data(links) .enter().append("line") - .attr("stroke-width", 2) + .attr("stroke-width", EDGE_STROKE_WIDTH) .attr("stroke", "#999") .on("click", function(event, d) { - const sourceNode = nodes.find(node => node.id === d.source.id); - const targetNode = nodes.find(node => node.id === d.target.id); - showModal(`Source: ${sourceNode.label}\nTarget: ${targetNode.label}`); + showEdgeModal(d); }); let node = svg.append("g") @@ -337,12 +342,10 @@ function updateGraph() { .selectAll("line") .data(links) .enter().append("line") - .attr("stroke-width", 2) + .attr("stroke-width", EDGE_STROKE_WIDTH) .attr("stroke", "#999") .on("click", function(event, d) { - const sourceNode = nodes.find(node => node.id === d.source.id); - const targetNode = nodes.find(node => node.id === d.target.id); - showModal(`Source: ${sourceNode.label}\nTarget: ${targetNode.label}`); + showEdgeModal(d); }); node = svg.append("g") @@ -405,22 +408,120 @@ const modal = document.getElementById("myModal"); const modalText = document.getElementById("modal-text"); const span = document.getElementsByClassName("close")[0]; -function showModal(content) { - modalText.textContent = content; - modal.style.display = "block"; -} - function generateBlockExplorerAddressLink(blockExplorerUrl, address, label) { return `${label || address}` } +function showEdgeModal(edge) { + const source = BridgeableScopes[edge.source.id] + const target = BridgeableScopes[edge.target.id] + + let maxAmount = Math.min( + balances[edge.source.id]?.[source.contractAddress] || 0, + balances[edge.target.id]?.[target.contractAddress] || 0, + balances[edge.source.id]?.[accounts[0]] || 0 + ) + + modalText.innerHTML = ` +

Bridge ${source.name} <-> ${target.name}

+

${source.name} Bridge Contract: ${generateBlockExplorerAddressLink(source.blockExplorerUrl, source.contractAddress)} (${(balances[edge.source.id]?.[source.contractAddress] || 0).toFixed(ETH_VALUE_PRECISION)} ETH)

+

${target.name} Bridge Contract: ${generateBlockExplorerAddressLink(target.blockExplorerUrl, target.contractAddress)} (${(balances[edge.target.id]?.[target.contractAddress] || 0).toFixed(ETH_VALUE_PRECISION)} ETH)

+
+ + + + +
+ + +
+ + + Max: ${maxAmount.toFixed(ETH_VALUE_PRECISION)} ETH +
+ + `; + + modal.style.display = "block"; + + document.getElementById("fromScopeSelect").addEventListener("change", (event) => { + const fromScope = event.target.value; + const toScope = fromScope === edge.source.id ? edge.target.id : edge.source.id; + document.getElementById("accountSelect").innerHTML = accounts.map(account => { + const balance = balances[fromScope]?.[account] || 0 + return `` + }).join('') + + document.getElementById("toScopeSelect").value = toScope + + const account = document.getElementById("accountSelect").value + maxAmount = Math.min( + balances[edge.source.id]?.[source.contractAddress] || 0, + balances[edge.target.id]?.[target.contractAddress] || 0, + balances[fromScope]?.[account] || 0 + ) + document.getElementById("maxAmountDisplay").innerText = maxAmount.toFixed(ETH_VALUE_PRECISION) + }); + + document.getElementById("toScopeSelect").addEventListener("change", (event) => { + const toScope = event.target.value; + const fromScope = toScope === edge.source.id ? edge.target.id : edge.source.id; + document.getElementById("fromScopeSelect").value = fromScope + }); + + document.getElementById("accountSelect").addEventListener("change", (event) => { + const account = event.target.value; + const fromScope = document.getElementById("fromScopeSelect").value + + maxAmount = Math.min( + balances[edge.source.id]?.[source.contractAddress] || 0, + balances[edge.target.id]?.[target.contractAddress] || 0, + balances[fromScope]?.[account] || 0 + ) + document.getElementById("maxAmountDisplay").innerText = maxAmount.toFixed(ETH_VALUE_PRECISION) + }); + + document.getElementById("bridgeButton").addEventListener("click", () => { + try { + const amount = Number(document.getElementById("amountText").value) + if (amount > maxAmount) { + alert(`Amount must be less than ${maxAmount}`) + return; + } + if (amount <= 0 || Number.isNaN(amount)) { + alert(`Amount must be greater than 0`) + return; + } + console.log('send') + + } catch (error) { + console.log(error) + alert('failed to bridge!') + } + }); +} + + function showNodeModal(id) { const {name, contractAddress, supports, blockExplorerUrl} = BridgeableScopes[id] modalText.innerHTML = `

${name}

Block Explorer

-

Bridge Contract: ${generateBlockExplorerAddressLink(blockExplorerUrl, contractAddress)} (${(balances[id]?.[contractAddress] || 0).toFixed(4)} ETH)

+

Bridge Contract: ${generateBlockExplorerAddressLink(blockExplorerUrl, contractAddress)} (${(balances[id]?.[contractAddress] || 0).toFixed(ETH_VALUE_PRECISION)} ETH)

Bridgeable Scopes: ${supports.map(scopeString => BridgeableScopes[scopeString].name).join(', ')}


Accounts: @@ -428,7 +529,7 @@ function showNodeModal(id) {
    ${accounts.map(account => { const balance = balances[id]?.[account] || 0 - return `
  • ${generateBlockExplorerAddressLink(blockExplorerUrl, account)} (${balance.toFixed(4)} ETH)
  • ` + return `
  • ${generateBlockExplorerAddressLink(blockExplorerUrl, account)} (${balance.toFixed(ETH_VALUE_PRECISION)} ETH)
  • ` }).join('')}
From fa36343cd42871aa139de3ddd6f1ecac84b8b8a0 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Fri, 1 Nov 2024 11:13:26 -0700 Subject: [PATCH 18/19] holy moly it works --- src/multichain_demo.js | 285 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 251 insertions(+), 34 deletions(-) diff --git a/src/multichain_demo.js b/src/multichain_demo.js index 5d1a15a5..6bed6142 100644 --- a/src/multichain_demo.js +++ b/src/multichain_demo.js @@ -1,8 +1,13 @@ import * as d3 from "d3"; +import { Contract } from 'web3-eth-contract'; +import abi from './multichain_demo_contract_abi.json' +const bridgeContract = new Contract(abi) // // Constants // +const WeiPerEth = 1000000000000000000 + const BridgeableScopes = { // Sepolia "eip155:11155111": { @@ -51,9 +56,11 @@ const NODE_PADDING = 20; const NODE_SCALE = 1.3; const NODE_TEXT_START = "-2.4em"; const NODE_TEXT_SPACING = "1.2em"; +const EDGE_COLOR = "#999"; const EDGE_STROKE_WIDTH = 3; +const FLASH_DURATION = 2000; -const ETH_VALUE_PRECISION = 4; +const ETH_VALUE_PRECISION = 3; const USE_SUBSCRIPTIONS = true; // use false for polling // subscriptions @@ -73,6 +80,7 @@ let accounts = []; let scopeStrings = []; let balances = {}; +let transactions = []; let subscriptionDebounce = {}; let nodes = []; @@ -211,6 +219,7 @@ async function connectWallet() { eip155: { references: ["11155111", "59141", "421614", "11155420", "168587773"], methods: [ + "eth_getTransactionReceipt", "eth_getBalance", "eth_sendTransaction", "eth_subscribe", @@ -303,7 +312,7 @@ let link = svg.append("g") .data(links) .enter().append("line") .attr("stroke-width", EDGE_STROKE_WIDTH) - .attr("stroke", "#999") + .attr("stroke", EDGE_COLOR) .on("click", function(event, d) { showEdgeModal(d); }); @@ -343,7 +352,7 @@ function updateGraph() { .data(links) .enter().append("line") .attr("stroke-width", EDGE_STROKE_WIDTH) - .attr("stroke", "#999") + .attr("stroke", EDGE_COLOR) .on("click", function(event, d) { showEdgeModal(d); }); @@ -495,7 +504,13 @@ function showEdgeModal(edge) { document.getElementById("maxAmountDisplay").innerText = maxAmount.toFixed(ETH_VALUE_PRECISION) }); - document.getElementById("bridgeButton").addEventListener("click", () => { + document.getElementById("bridgeButton").addEventListener("click", async () => { + const account = document.getElementById("accountSelect").value + const fromScope = document.getElementById("fromScopeSelect").value + const toScope = document.getElementById("toScopeSelect").value + + const { contractAddress } = BridgeableScopes[fromScope] + try { const amount = Number(document.getElementById("amountText").value) if (amount > maxAmount) { @@ -506,8 +521,34 @@ function showEdgeModal(edge) { alert(`Amount must be greater than 0`) return; } - console.log('send') + + const transactionHash = await extensionPortRequest({ + method: "wallet_invokeMethod", + params: { + scope: fromScope, + request: { + "method": "eth_sendTransaction", + "params": [{ + to: contractAddress, + from: account, + data: "0x", + value: `0x${(amount * WeiPerEth).toString(16)}` + }] + } + }, + }) + + transactions.push({ + account, + toScope, + fromScope, + amount, + transactionHash, + type: 'bridge' + }) + updateEdge(fromScope, toScope, "black") + modal.style.display = "none"; } catch (error) { console.log(error) alert('failed to bridge!') @@ -547,36 +588,35 @@ window.onclick = function(event) { } } -// Flash an edge -// setTimeout(() => { -// const randomLink = d3.select(link.nodes()[Math.floor(Math.random() * links.length)]); -// const originalColor = randomLink.attr("stroke"); -// randomLink.attr("stroke", "green"); -// setTimeout(() => { -// randomLink.attr("stroke", originalColor); -// }, 3000); -// }, 5000); +function updateEdge(sourceId, targetId, color = EDGE_COLOR) { + const targetLink = links.find(link => + (link.source.id === sourceId && link.target.id === targetId) || + (link.source.id === targetId && link.target.id === sourceId) + ) + if (!targetLink) { + console.log('couldnt find link', {sourceId, targetId, color}) + return + } + const linkSelection = d3.select(link.nodes()[links.indexOf(targetLink)]) -// setTimeout(() => { -// const randomLink = links[Math.floor(Math.random() * links.length)]; -// const linkSelection = d3.select(link.nodes()[links.indexOf(randomLink)]); -// const sourceNode = nodes.find(node => node.id === randomLink.source.id); -// const targetNode = nodes.find(node => node.id === randomLink.target.id); + linkSelection.attr("stroke", color); -// const edgeLabel = svg.append("text") -// .attr("x", (sourceNode.x + targetNode.x) / 2) -// .attr("y", (sourceNode.y + targetNode.y) / 2) -// .attr("dy", -5) -// .attr("text-anchor", "middle") -// .attr("fill", "green") -// .text("sending..."); + // const sourceNode = nodes.find(node => node.id === randomLink.source.id); + // const targetNode = nodes.find(node => node.id === randomLink.target.id); -// setTimeout(() => { -// edgeLabel.remove(); -// }, 3000); -// }, 5000); + // const edgeLabel = svg.append("text") + // .attr("x", (sourceNode.x + targetNode.x) / 2) + // .attr("y", (sourceNode.y + targetNode.y) / 2) + // .attr("dy", -5) + // .attr("text-anchor", "middle") + // .attr("fill", "green") + // .text("sending..."); + // setTimeout(() => { + // edgeLabel.remove(); + // }, 3000); +} function addNode(id) { // console.log('adding node', id) @@ -672,7 +712,7 @@ function updateNode(id, color) { targetNodeCircle.attr("stroke", color).attr("stroke-width", 3); setTimeout(() => { targetNodeCircle.attr("stroke", null).attr("stroke-width", null); - }, 2000); + }, FLASH_DURATION); } // Restart simulation to adjust positions simulation.alpha(1).restart(); @@ -682,6 +722,38 @@ function updateNode(id, color) { document.getElementById("connectExtensionButton").addEventListener("click", connectExtension); document.getElementById("connectButton").addEventListener("click", connectWallet); +// Claim +async function claimBridgedEth(transaction) { + const { toScope, fromScope, account, amount } = transaction + const { contractAddress } = BridgeableScopes[toScope] + + const data = await bridgeContract.methods.withdraw(`0x${(amount * WeiPerEth).toString(16)}`).encodeABI(); + + const transactionHash = await extensionPortRequest({ + method: "wallet_invokeMethod", + params: { + scope: toScope, + request: { + "method": "eth_sendTransaction", + "params": [{ + to: contractAddress, + from: account, + data + }] + } + }, + }) + + transactions.push({ + account, + toScope, + fromScope, + amount, + transactionHash, + type: 'claim' + }) + updateEdge(fromScope, toScope, "black") +} // Events/Loops async function handleEthSubscription(scopeString, _blockHead) { @@ -694,9 +766,10 @@ async function handleEthSubscription(scopeString, _blockHead) { } subscriptionDebounce[scopeString] = true - console.log(`Subscription: updating account balances and contract balance for scope ${scopeString}`) - await updateAccountBalancesForScope(scopeString) + console.log(`Subscription: updating account balances, contract balance, tx statuses for scope ${scopeString}`) + await updateTransactionStatusesForScope(scopeString) await updateContractBalanceForScope(scopeString) + await updateAccountBalancesForScope(scopeString) setTimeout(() => { subscriptionDebounce[scopeString] = false @@ -880,11 +953,155 @@ async function updateContractBalancesPoll() { }, POLLING_RATE) } +async function updateTransactionStatusesForScope(scopeString) { + if(!extensionPort) { + return + } + if(!accounts.length || !scopeStrings.length) { + return + } + + const pendingTransactions = transactions.filter(transaction => !transaction.status) + + await Promise.allSettled(pendingTransactions.map(async pendingTransaction => { + const {fromScope, toScope, type, transactionHash } = pendingTransaction + + const targetScope = type === 'bridge' ? fromScope : toScope + + if(targetScope !== scopeString) { + return + } + + console.log(`getting tx receipt for tx hash ${transactionHash} on scope ${targetScope}`) + try { + const receipt = await extensionPortRequest({ + method: "wallet_invokeMethod", + params: { + scope: targetScope, + request: { + "method": "eth_getTransactionReceipt", + "params": [ + transactionHash + ], + }, + }, + }) + + if(!receipt) { + return; + } + + pendingTransaction.status = receipt.status + updateEdge(fromScope, toScope, receipt.status === '0x1' ? 'green' : 'red') + setTimeout(() => { + updateEdge(fromScope, toScope) + }, FLASH_DURATION) + if (pendingTransaction.type === 'bridge') { + setTimeout(() => { + claimBridgedEth(pendingTransaction) + }, FLASH_DURATION + 1000) + } + console.log(`got tx receipt for tx hash ${transactionHash} on scope ${targetScope}`, receipt.status) + } catch (error) { + console.error(`failed getting tx receipt for tx hash ${transactionHash} on scope ${targetScope}`) + } + })) +} + +async function updateTransactionStatuses() { + if(!extensionPort) { + return + } + if(!accounts.length || !scopeStrings.length) { + return + } + + const pendingTransactions = transactions.filter(transaction => !transaction.status) + + await Promise.allSettled(pendingTransactions.map(async pendingTransaction => { + const {fromScope, toScope, type, transactionHash } = pendingTransaction + + const targetScope = type === 'bridge' ? fromScope : toScope + + console.log(`getting tx receipt for tx hash ${transactionHash} on scope ${targetScope}`) + try { + const receipt = await extensionPortRequest({ + method: "wallet_invokeMethod", + params: { + scope: targetScope, + request: { + "method": "eth_getTransactionReceipt", + "params": [ + transactionHash + ], + }, + }, + }) + + if(!receipt) { + return; + } + + pendingTransaction.status = receipt.status + updateEdge(fromScope, toScope, receipt.status === '0x1' ? 'green' : 'red') + setTimeout(() => { + updateEdge(fromScope, toScope) + }, FLASH_DURATION) + if (pendingTransaction.type === 'bridge') { + setTimeout(() => { + claimBridgedEth(pendingTransaction) + }, FLASH_DURATION + 1000) + } + console.log(`got tx receipt for tx hash ${transactionHash} on scope ${targetScope}`, receipt.status) + } catch (error) { + console.error(`failed getting tx receipt for tx hash ${transactionHash} on scope ${targetScope}`) + } + })) +} +async function updateTransactionStatusesPoll() { + console.log("Polling: updating all tx statuses") + await updateTransactionStatuses() + setTimeout(() => { + updateTransactionStatusesPoll() + }, POLLING_RATE) +} + let isPolling; async function startPolling() { if (!isPolling) { isPolling = true - updateAccountBalancesPoll() + updateTransactionStatusesPoll() updateContractBalancesPoll() + updateAccountBalancesPoll() } } + +// Flash an edge +// setTimeout(() => { +// const randomLink = d3.select(link.nodes()[Math.floor(Math.random() * links.length)]); +// const originalColor = randomLink.attr("stroke"); +// randomLink.attr("stroke", "green"); +// setTimeout(() => { +// randomLink.attr("stroke", originalColor); +// }, 3000); +// }, 5000); + + +// setTimeout(() => { +// const randomLink = links[Math.floor(Math.random() * links.length)]; +// const linkSelection = d3.select(link.nodes()[links.indexOf(randomLink)]); +// const sourceNode = nodes.find(node => node.id === randomLink.source.id); +// const targetNode = nodes.find(node => node.id === randomLink.target.id); + +// const edgeLabel = svg.append("text") +// .attr("x", (sourceNode.x + targetNode.x) / 2) +// .attr("y", (sourceNode.y + targetNode.y) / 2) +// .attr("dy", -5) +// .attr("text-anchor", "middle") +// .attr("fill", "green") +// .text("sending..."); + +// setTimeout(() => { +// edgeLabel.remove(); +// }, 3000); +// }, 5000); From d12271af9d66d452fc4a29011f85ed527e205703 Mon Sep 17 00:00:00 2001 From: Jiexi Luan Date: Fri, 1 Nov 2024 13:37:45 -0700 Subject: [PATCH 19/19] "polish" haha. I'm out of time, but I'm happy with this --- src/multichain_demo.js | 120 ++++++++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 38 deletions(-) diff --git a/src/multichain_demo.js b/src/multichain_demo.js index 6bed6142..74bc3953 100644 --- a/src/multichain_demo.js +++ b/src/multichain_demo.js @@ -52,15 +52,17 @@ const pastelColors = [ "#9ac7a8", "#b8dbc9", "#c5e3d2", "#d1ebdb", "#d6e1c8", "#e5c28c", "#e8d097", "#edd9aa", "#f2e6bb", "#f9f3dd" ]; +const NODE_MIN_RADIUS = 130; const NODE_PADDING = 20; const NODE_SCALE = 1.3; const NODE_TEXT_START = "-2.4em"; const NODE_TEXT_SPACING = "1.2em"; +const NODE_FLASH_COLOR = "gray" const EDGE_COLOR = "#999"; const EDGE_STROKE_WIDTH = 3; -const FLASH_DURATION = 2000; +const FLASH_DURATION = 3000; -const ETH_VALUE_PRECISION = 3; +const ETH_VALUE_PRECISION = 4; const USE_SUBSCRIPTIONS = true; // use false for polling // subscriptions @@ -163,7 +165,7 @@ async function onNewSessionScopes(sessionScopes) { }, Math.floor(Math.random() * (SUBSCRIPTION_STAGGER + 1))) } } else if (accountsDidChange) { - updateNode(newScopeString, "black") + refreshNodeContent(newScopeString) } }) @@ -197,12 +199,13 @@ function getShortAddress(address) { function getNodeLabelText(scopeString) { const {name, contractAddress} = BridgeableScopes[scopeString]; const accountsText = accounts.map(account => { - const balance = balances[scopeString]?.[account] || 0 - return `${getShortAddress(account)}: ${balance.toFixed(ETH_VALUE_PRECISION)} ETH` + const balance = balances[scopeString]?.[account]?.toFixed(ETH_VALUE_PRECISION) ?? '------' + return `${getShortAddress(account)}: ${balance} ETH` }).join('\n') const contractBalance = balances[scopeString]?.[contractAddress] || 0 - return`${name}\nBridge Contract: ${contractBalance.toFixed(ETH_VALUE_PRECISION)} ETH\n--------------------------\n${accountsText}` + // return`${name}\nBridge Contract: ${contractBalance.toFixed(ETH_VALUE_PRECISION)} ETH\n--------------------------\n${accountsText}` + return`${name}\n--------------------------\n${accountsText}` } // Permission Initialization @@ -304,7 +307,7 @@ const simulation = d3.forceSimulation(nodes) .force("link", d3.forceLink(links).id(d => d.id).distance(400)) // Further increased distance .force("charge", d3.forceManyBody().strength(-1600)) // Further increased negative strength .force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2)) - .force("collide", d3.forceCollide().radius(d => getRadius(d.label) * NODE_SCALE + NODE_PADDING + 40)); // Further increased radius + .force("collide", d3.forceCollide().radius(d => getRadius(d.label) + 40)); // Further increased radius let link = svg.append("g") .attr("class", "links") @@ -327,7 +330,7 @@ let node = svg.append("g") }); node.append("circle") - .attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING) + .attr("r", d => getRadius(d.label)) .attr("fill", () => pastelColors[Math.floor(Math.random() * pastelColors.length)]); let text = node.append("text") @@ -367,7 +370,7 @@ function updateGraph() { }); node.append("circle") - .attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING) + .attr("r", d => getRadius(d.label)) .attr("fill", () => pastelColors[Math.floor(Math.random() * pastelColors.length)]); text = node.append("text") @@ -402,7 +405,8 @@ function getRadius(text) { const maxWidth = Math.max(...lines.map(line => context.measureText(line).width)); const lineHeight = 10; // Approximate line height const textHeight = lines.length * lineHeight; - return Math.max(20, Math.sqrt(maxWidth * maxWidth + textHeight * textHeight) / 2 + 10); // Ensure a minimum radius of 20 + const original = Math.sqrt(maxWidth * maxWidth + textHeight * textHeight) / 2 + 10 + return Math.max(NODE_MIN_RADIUS, original * NODE_SCALE + NODE_PADDING); } window.addEventListener("resize", () => { @@ -459,7 +463,7 @@ function showEdgeModal(edge) {
- + Max: ${maxAmount.toFixed(ETH_VALUE_PRECISION)} ETH
@@ -547,7 +551,8 @@ function showEdgeModal(edge) { transactionHash, type: 'bridge' }) - updateEdge(fromScope, toScope, "black") + updateEdgeColor(fromScope, toScope, "orange") + updateNodeBorderColor(fromScope, "orange") modal.style.display = "none"; } catch (error) { console.log(error) @@ -588,7 +593,7 @@ window.onclick = function(event) { } } -function updateEdge(sourceId, targetId, color = EDGE_COLOR) { +function updateEdgeColor(sourceId, targetId, color = EDGE_COLOR) { const targetLink = links.find(link => (link.source.id === sourceId && link.target.id === targetId) || (link.source.id === targetId && link.target.id === sourceId) @@ -651,7 +656,7 @@ function addNode(id) { // }); // newNodeSelection.append("circle") - // .attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING) + // .attr("r", d => getRadius(d.label)) // .attr("fill", () => pastelColors[Math.floor(Math.random() * pastelColors.length)]); // const newText = newNodeSelection.append("text") @@ -694,7 +699,7 @@ function removeNode(id) { simulation.alpha(1).restart(); } -function updateNode(id, color) { +function refreshNodeContent(id) { const targetNode = nodes.find(node => node.id === id) targetNode.label = getNodeLabelText(id) text.selectAll("tspan").remove(); @@ -705,17 +710,41 @@ function updateNode(id, color) { .attr("x", 0) .attr("dy", (d, i) => i === 0 ? NODE_TEXT_START : NODE_TEXT_SPACING) .text(d => d); - node.select("circle").attr("r", d => getRadius(d.label) * NODE_SCALE + NODE_PADDING); - - if (color) { - const targetNodeCircle = d3.select(node.nodes()[nodes.indexOf(targetNode)]).select("circle"); - targetNodeCircle.attr("stroke", color).attr("stroke-width", 3); - setTimeout(() => { - targetNodeCircle.attr("stroke", null).attr("stroke-width", null); - }, FLASH_DURATION); + node.select("circle").attr("r", d => getRadius(d.label)); + + const originalColor = svg.selectAll(".nodes circle") + .filter(d => d.id === id) + .attr("fill"); + + if(originalColor !== NODE_FLASH_COLOR) { + svg.selectAll(".nodes circle") + .filter(d => d.id === id) + .attr("fill", NODE_FLASH_COLOR); + + setTimeout(() => { + svg.selectAll(".nodes circle") + .filter(d => d.id === id) + .attr("fill", originalColor); + }, FLASH_DURATION / 4) } + + // Restart simulation to adjust positions - simulation.alpha(1).restart(); + // simulation.alpha(1).restart(); +} + +function updateNodeBorderColor(id, color) { + const targetNode = nodes.find(node => node.id === id) + + const targetNodeCircle = d3.select(node.nodes()[nodes.indexOf(targetNode)]).select("circle"); + if (color) { + targetNodeCircle.attr("stroke", color).attr("stroke-width", 3); + } else { + targetNodeCircle.attr("stroke", null).attr("stroke-width", null); + } + + // Restart simulation to adjust positions + // simulation.alpha(1).restart(); } // DOM @@ -727,6 +756,11 @@ async function claimBridgedEth(transaction) { const { toScope, fromScope, account, amount } = transaction const { contractAddress } = BridgeableScopes[toScope] + if (!confirm(`Initial bridge tranasction on ${BridgeableScopes[fromScope].name} was successful. Finish by claiming the ${amount} ETH now waiting on ${BridgeableScopes[toScope].name}?`)) { + console.log(`User chose not to finish claiming ${amount} ETH that has already been bridged from ${BridgeableScopes[fromScope].name} to ${BridgeableScopes[toScope].name}`); + return + } + const data = await bridgeContract.methods.withdraw(`0x${(amount * WeiPerEth).toString(16)}`).encodeABI(); const transactionHash = await extensionPortRequest({ @@ -752,7 +786,8 @@ async function claimBridgedEth(transaction) { transactionHash, type: 'claim' }) - updateEdge(fromScope, toScope, "black") + updateEdgeColor(fromScope, toScope, "orange") + updateNodeBorderColor(toScope, "orange") } // Events/Loops @@ -806,7 +841,8 @@ async function updateAccountBalancesForScope(scopeString) { if (oldBalance !== balances[scopeString][account]) { // console.log(`updating node for account ${account} on scope ${scopeString} with ${balances[scopeString][account]}`) - updateNode(scopeString, !oldBalance || balances[scopeString][account] > oldBalance ? "green" : "red") + // refreshNodeContent(scopeString, !oldBalance || balances[scopeString][account] > oldBalance ? "green" : "red") + refreshNodeContent(scopeString) } } catch (error) { @@ -849,7 +885,8 @@ async function updateAccountBalances() { if (oldBalance !== balances[scopeString][account]) { // console.log(`updating node for account ${account} on scope ${scopeString} with ${balances[scopeString][account]}`) - updateNode(scopeString, !oldBalance || balances[scopeString][account] > oldBalance ? "green" : "red") + // refreshNodeContent(scopeString, !oldBalance || balances[scopeString][account] > oldBalance ? "green" : "red") + refreshNodeContent(scopeString) } } catch (error) { @@ -897,7 +934,7 @@ async function updateContractBalanceForScope(scopeString) { if (oldBalance !== balances[scopeString][contractAddress]) { // console.log(`updating node for contract ${contractAddress} on scope ${scopeString} with ${balances[scopeString][contractAddress]}`) - updateNode(scopeString) + refreshNodeContent(scopeString) } } catch (error) { @@ -937,7 +974,7 @@ async function updateContractBalances() { if (oldBalance !== balances[scopeString][contractAddress]) { // console.log(`updating node for contract ${contractAddress} on scope ${scopeString} with ${balances[scopeString][contractAddress]}`) - updateNode(scopeString) + refreshNodeContent(scopeString) } } catch (error) { @@ -966,7 +1003,8 @@ async function updateTransactionStatusesForScope(scopeString) { await Promise.allSettled(pendingTransactions.map(async pendingTransaction => { const {fromScope, toScope, type, transactionHash } = pendingTransaction - const targetScope = type === 'bridge' ? fromScope : toScope + const isBridge = type === 'bridge' + const targetScope = isBridge ? fromScope : toScope if(targetScope !== scopeString) { return @@ -992,14 +1030,17 @@ async function updateTransactionStatusesForScope(scopeString) { } pendingTransaction.status = receipt.status - updateEdge(fromScope, toScope, receipt.status === '0x1' ? 'green' : 'red') + const color = receipt.status === '0x1' ? 'green' : 'red' + updateEdgeColor(fromScope, toScope, color) + updateNodeBorderColor(isBridge ? fromScope : toScope, color) setTimeout(() => { - updateEdge(fromScope, toScope) + updateEdgeColor(fromScope, toScope) + updateNodeBorderColor(isBridge ? fromScope : toScope) }, FLASH_DURATION) - if (pendingTransaction.type === 'bridge') { + if (isBridge) { setTimeout(() => { claimBridgedEth(pendingTransaction) - }, FLASH_DURATION + 1000) + }, FLASH_DURATION + 500) } console.log(`got tx receipt for tx hash ${transactionHash} on scope ${targetScope}`, receipt.status) } catch (error) { @@ -1043,14 +1084,17 @@ async function updateTransactionStatuses() { } pendingTransaction.status = receipt.status - updateEdge(fromScope, toScope, receipt.status === '0x1' ? 'green' : 'red') + const color = receipt.status === '0x1' ? 'green' : 'red' + updateEdgeColor(fromScope, toScope, color) + updateNodeBorderColor(isBridge ? fromScope : toScope, color) setTimeout(() => { - updateEdge(fromScope, toScope) + updateEdgeColor(fromScope, toScope) + updateNodeBorderColor(isBridge ? fromScope : toScope) }, FLASH_DURATION) - if (pendingTransaction.type === 'bridge') { + if (isBridge) { setTimeout(() => { claimBridgedEth(pendingTransaction) - }, FLASH_DURATION + 1000) + }, FLASH_DURATION + 500) } console.log(`got tx receipt for tx hash ${transactionHash} on scope ${targetScope}`, receipt.status) } catch (error) {