diff --git a/package.json b/package.json
index 24240ed3..e0908dbc 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,10 @@
"url": "https://github.com/MetaMask/test-dapp/issues"
},
"homepage": "https://metamask.github.io/test-dapp",
- "dependencies": {},
+ "dependencies": {
+ "d3": "^7.9.0",
+ "web3-eth-contract": "^4.7.0"
+ },
"devDependencies": {
"@lavamoat/allow-scripts": "^2.5.1",
"@lavamoat/preinstall-always-fail": "^2.0.0",
diff --git a/src/eip1193_index.html b/src/eip1193_index.html
new file mode 100644
index 00000000..7ce9c8d5
--- /dev/null
+++ b/src/eip1193_index.html
@@ -0,0 +1,209 @@
+
+
+
+
+
+ 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_demo.html b/src/multichain_demo.html
new file mode 100644
index 00000000..46f6c933
--- /dev/null
+++ b/src/multichain_demo.html
@@ -0,0 +1,88 @@
+
+
+
+
+
+ Multichain API EVM Bridging Demo Dapp
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/multichain_demo.js b/src/multichain_demo.js
new file mode 100644
index 00000000..74bc3953
--- /dev/null
+++ b/src/multichain_demo.js
@@ -0,0 +1,1151 @@
+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": {
+ 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_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 = 3000;
+
+const ETH_VALUE_PRECISION = 4;
+
+const USE_SUBSCRIPTIONS = true; // use false for polling
+// subscriptions
+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
+
+//
+// State
+//
+let extensionPort;
+let jsonRpcId;
+let currentSessionScopes = {};
+let accounts = [];
+let scopeStrings = [];
+
+let balances = {};
+let transactions = [];
+let subscriptionDebounce = {};
+
+let nodes = [];
+let links = [];
+
+//
+// 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") {
+ handleEthSubscription(
+ 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];
+
+ 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)
+ if (USE_SUBSCRIPTIONS) {
+ setTimeout(() => {
+ subscribeToBlockHeaders(newScopeString)
+ }, Math.floor(Math.random() * (SUBSCRIPTION_STAGGER + 1)))
+ }
+ } else if (accountsDidChange) {
+ refreshNodeContent(newScopeString)
+ }
+ })
+
+ if (!USE_SUBSCRIPTIONS) {
+ 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 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]?.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}\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_getTransactionReceipt",
+ "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) + 40)); // Further increased radius
+
+let link = svg.append("g")
+ .attr("class", "links")
+ .selectAll("line")
+ .data(links)
+ .enter().append("line")
+ .attr("stroke-width", EDGE_STROKE_WIDTH)
+ .attr("stroke", EDGE_COLOR)
+ .on("click", function(event, d) {
+ showEdgeModal(d);
+ });
+
+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))
+ .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", EDGE_STROKE_WIDTH)
+ .attr("stroke", EDGE_COLOR)
+ .on("click", function(event, d) {
+ showEdgeModal(d);
+ });
+
+ 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))
+ .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;
+ const original = Math.sqrt(maxWidth * maxWidth + textHeight * textHeight) / 2 + 10
+ return Math.max(NODE_MIN_RADIUS, original * NODE_SCALE + NODE_PADDING);
+}
+
+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 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", 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) {
+ alert(`Amount must be less than ${maxAmount}`)
+ return;
+ }
+ if (amount <= 0 || Number.isNaN(amount)) {
+ alert(`Amount must be greater than 0`)
+ return;
+ }
+
+
+ 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'
+ })
+ updateEdgeColor(fromScope, toScope, "orange")
+ updateNodeBorderColor(fromScope, "orange")
+ modal.style.display = "none";
+ } 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(ETH_VALUE_PRECISION)} ETH)
+ Bridgeable Scopes: ${supports.map(scopeString => BridgeableScopes[scopeString].name).join(', ')}
+
+ Accounts:
+
+
+ ${accounts.map(account => {
+ const balance = balances[id]?.[account] || 0
+ return `- ${generateBlockExplorerAddressLink(blockExplorerUrl, account)} (${balance.toFixed(ETH_VALUE_PRECISION)} 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";
+ }
+}
+
+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)
+ )
+ if (!targetLink) {
+ console.log('couldnt find link', {sourceId, targetId, color})
+ return
+ }
+
+ const linkSelection = d3.select(link.nodes()[links.indexOf(targetLink)])
+
+ linkSelection.attr("stroke", color);
+
+ // 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);
+}
+
+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))
+ // .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 refreshNodeContent(id) {
+ 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));
+
+ 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();
+}
+
+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
+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]
+
+ 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({
+ 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'
+ })
+ updateEdgeColor(fromScope, toScope, "orange")
+ updateNodeBorderColor(toScope, "orange")
+}
+
+// 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, contract balance, tx statuses for scope ${scopeString}`)
+ await updateTransactionStatusesForScope(scopeString)
+ await updateContractBalanceForScope(scopeString)
+ await updateAccountBalancesForScope(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]}`)
+ // refreshNodeContent(scopeString, !oldBalance || balances[scopeString][account] > oldBalance ? "green" : "red")
+ refreshNodeContent(scopeString)
+ }
+
+ } 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
+ }
+ if(!accounts.length || !scopeStrings.length) {
+ return
+ }
+
+ 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}`)
+ 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]}`)
+ // refreshNodeContent(scopeString, !oldBalance || balances[scopeString][account] > oldBalance ? "green" : "red")
+ refreshNodeContent(scopeString)
+ }
+
+ } catch (error) {
+ console.error(`failed updating balance for account ${account} on scope ${scopeString}`, error)
+ }
+
+ }))
+ }
+}
+async function updateAccountBalancesPoll() {
+ await updateAccountBalances()
+ setTimeout(() => {
+ updateAccountBalancesPoll()
+ }, 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]}`)
+ refreshNodeContent(scopeString)
+ }
+
+ } catch (error) {
+ console.error(`failed updating balance for contract ${contractAddress} on scope ${scopeString}`, error)
+ }
+}
+
+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]}`)
+ refreshNodeContent(scopeString)
+ }
+
+ } catch (error) {
+ console.error(`failed updating balance for contract ${contractAddress} on scope ${scopeString}`, error)
+ }
+ }))
+}
+async function updateContractBalancesPoll() {
+ console.log("Polling: updating all contract balances")
+ await updateContractBalances()
+ setTimeout(() => {
+ 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 isBridge = type === 'bridge'
+ const targetScope = isBridge ? 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
+ const color = receipt.status === '0x1' ? 'green' : 'red'
+ updateEdgeColor(fromScope, toScope, color)
+ updateNodeBorderColor(isBridge ? fromScope : toScope, color)
+ setTimeout(() => {
+ updateEdgeColor(fromScope, toScope)
+ updateNodeBorderColor(isBridge ? fromScope : toScope)
+ }, FLASH_DURATION)
+ if (isBridge) {
+ setTimeout(() => {
+ claimBridgedEth(pendingTransaction)
+ }, FLASH_DURATION + 500)
+ }
+ 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
+ const color = receipt.status === '0x1' ? 'green' : 'red'
+ updateEdgeColor(fromScope, toScope, color)
+ updateNodeBorderColor(isBridge ? fromScope : toScope, color)
+ setTimeout(() => {
+ updateEdgeColor(fromScope, toScope)
+ updateNodeBorderColor(isBridge ? fromScope : toScope)
+ }, FLASH_DURATION)
+ if (isBridge) {
+ setTimeout(() => {
+ claimBridgedEth(pendingTransaction)
+ }, FLASH_DURATION + 500)
+ }
+ 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
+ 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);
diff --git a/src/multichain_demo_contract.sol b/src/multichain_demo_contract.sol
new file mode 100644
index 00000000..9e84a446
--- /dev/null
+++ b/src/multichain_demo_contract.sol
@@ -0,0 +1,54 @@
+// 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;
+ }
+
+ receive() external payable {
+ 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..48b9f99a
--- /dev/null
+++ b/src/multichain_demo_contract_abi.json
@@ -0,0 +1,163 @@
+[
+ {
+ "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": "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"
+ },
+ {
+ "stateMutability": "payable",
+ "type": "receive"
+ }
+]
diff --git a/src/multichain_demo_contract_bytecode.txt b/src/multichain_demo_contract_bytecode.txt
new file mode 100644
index 00000000..2d0daa1c
--- /dev/null
+++ b/src/multichain_demo_contract_bytecode.txt
@@ -0,0 +1 @@
+608060405234801561001057600080fd5b50604051610d39380380610d39833981810160405281019061003291906102d6565b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060005b81518110156100fc5760018060008484815181106100965761009561031f565b5b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508080600101915050610075565b5080516002819055505061034e565b6000604051905090565b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61016d82610124565b810181811067ffffffffffffffff8211171561018c5761018b610135565b5b80604052505050565b600061019f61010b565b90506101ab8282610164565b919050565b600067ffffffffffffffff8211156101cb576101ca610135565b5b602082029050602081019050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061020c826101e1565b9050919050565b61021c81610201565b811461022757600080fd5b50565b60008151905061023981610213565b92915050565b600061025261024d846101b0565b610195565b90508083825260208201905060208402830185811115610275576102746101dc565b5b835b8181101561029e578061028a888261022a565b845260208401935050602081019050610277565b5050509392505050565b600082601f8301126102bd576102bc61011f565b5b81516102cd84826020860161023f565b91505092915050565b6000602082840312156102ec576102eb610115565b5b600082015167ffffffffffffffff81111561030a5761030961011a565b5b610316848285016102a8565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6109dc8061035d6000396000f3fe6080604052600436106100595760003560e01c80630e73684e146100b35780632e1a7d4d146100dc5780634120657a146101055780636f9fb98a1461014257806389d34e4b1461016d5780638da5cb5b14610198576100ae565b366100ae573373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040516100a491906105bb565b60405180910390a2005b600080fd5b3480156100bf57600080fd5b506100da60048036038101906100d59190610671565b6101c3565b005b3480156100e857600080fd5b5061010360048036038101906100fe91906106dd565b6103e9565b005b34801561011157600080fd5b5061012c6004803603810190610127919061070a565b610550565b6040516101399190610746565b60405180910390f35b34801561014e57600080fd5b50610157610570565b60405161016491906105bb565b60405180910390f35b34801561017957600080fd5b50610182610578565b60405161018f91906105bb565b60405180910390f35b3480156101a457600080fd5b506101ad61057e565b6040516101ba9190610770565b60405180910390f35b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610251576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102489061080e565b60405180910390fd5b8080156102a85750600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16155b156102ca57600260008154809291906102c09061085d565b9190505550610340565b801580156103215750600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff165b1561033f5760026000815480929190610339906108a5565b91905055505b5b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff167f14fcc60af8440546881d552eb01644aa747a1106cb912daf1a3b52e9932ed189826040516103dd9190610746565b60405180910390a25050565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16610475576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161046c9061091a565b60405180910390fd5b804710156104b8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104af90610986565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501580156104fe573d6000803e3d6000fd5b503373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b658260405161054591906105bb565b60405180910390a250565b60016020528060005260406000206000915054906101000a900460ff1681565b600047905090565b60025481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000819050919050565b6105b5816105a2565b82525050565b60006020820190506105d060008301846105ac565b92915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610606826105db565b9050919050565b610616816105fb565b811461062157600080fd5b50565b6000813590506106338161060d565b92915050565b60008115159050919050565b61064e81610639565b811461065957600080fd5b50565b60008135905061066b81610645565b92915050565b60008060408385031215610688576106876105d6565b5b600061069685828601610624565b92505060206106a78582860161065c565b9150509250929050565b6106ba816105a2565b81146106c557600080fd5b50565b6000813590506106d7816106b1565b92915050565b6000602082840312156106f3576106f26105d6565b5b6000610701848285016106c8565b91505092915050565b6000602082840312156107205761071f6105d6565b5b600061072e84828501610624565b91505092915050565b61074081610639565b82525050565b600060208201905061075b6000830184610737565b92915050565b61076a816105fb565b82525050565b60006020820190506107856000830184610761565b92915050565b600082825260208201905092915050565b7f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f60008201527f6e00000000000000000000000000000000000000000000000000000000000000602082015250565b60006107f860218361078b565b91506108038261079c565b604082019050919050565b60006020820190508181036000830152610827816107eb565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610868826105a2565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361089a5761089961082e565b5b600182019050919050565b60006108b0826105a2565b9150600082036108c3576108c261082e565b5b600182039050919050565b7f4e6f7420616c6c6f77656420746f207769746864726177000000000000000000600082015250565b600061090460178361078b565b915061090f826108ce565b602082019050919050565b60006020820190508181036000830152610933816108f7565b9050919050565b7f496e73756666696369656e742062616c616e6365000000000000000000000000600082015250565b600061097060148361078b565b915061097b8261093a565b602082019050919050565b6000602082019050818103600083015261099f81610963565b905091905056fea26469706673582212204c5c5bc49f588ae9c18b9af6ec048481d900a81336d2241578883700d803512164736f6c634300081a0033
diff --git a/src/multichain_index.html b/src/multichain_index.html
new file mode 100644
index 00000000..8342884c
--- /dev/null
+++ b/src/multichain_index.html
@@ -0,0 +1,417 @@
+
+
+
+
+
+ Ethereum Multichain API Dapp
+
+
+ Ethereum Multichain API Dapp
+
+
+
+
+ Connected Extension: Not available
+
+
+
+
+
+
+
+
+
+
+ Connected Accounts: Not available
+
+
+
+
+
+
+
+
+
+
+
+
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==