<!DOCTYPE html> <html data-bs-theme="dark" lang="en-au"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <title>Vault | Woodburn</title> <meta name="theme-color" content="#ffffff"> <meta name="twitter:description" content="Woodburn Vault"> <meta name="twitter:image" content="https://sol.woodburn.au/assets/img/favicon.png"> <meta property="og:description" content="Woodburn Vault"> <meta property="og:type" content="website"> <meta property="og:title" content="Vault | Woodburn"> <meta name="twitter:card" content="summary"> <meta name="twitter:title" content="Vault | Woodburn"> <meta name="description" content="Woodburn Vault"> <meta property="og:image" content="https://sol.woodburn.au/assets/img/favicon.png"> <script type="application/ld+json"> { "@context": "http://schema.org", "@type": "WebSite", "name": "Vault | Woodburn", "url": "https://sol.woodburn.au" } </script> <link rel="icon" type="image/png" sizes="192x192" href="assets/img/favicon-192.png"> <link rel="icon" type="image/png" sizes="512x512" href="assets/img/favicon.png"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="manifest" href="manifest.json" crossorigin="use-credentials"> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Catamaran:100,200,300,400,500,600,700,800,900&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato:100,100i,300,300i,400,400i,700,700i,900,900i&display=swap"> <link rel="stylesheet" href="assets/fonts/font-awesome.min.css"> </head> <body> <nav class="navbar navbar-expand-lg fixed-top bg-dark navbar-custom navbar-dark"> <div class="container"><a class="navbar-brand" href="#">Vault | Woodburn</a></div> </nav> <header class="text-center text-white masthead"> <div class="masthead-content"> <div class="container"> <h1 class="masthead-heading mb-0">Woodburn Vault</h1> <h2 class="masthead-subheading mb-0">An easy way to buy into a diverse crypto portfolio.</h2> <p>Woodburn Vault Balance: {{vault}} USD ({{vault_aud}} AUD)<br>stWDBRN Token Supply: {{supply}}<br>Current Token Value: {{value}} USD ({{value_aud}} AUD)</p> <div style="display: inline-flex;"> <div class="input-group" style="width: fit-content;margin: 10px;"><span class="input-group-text">USD</span><input class="form-control" type="text" id="fiat-input" name="fiat" oninput="convert('fiat')" value="1"><button class="btn btn-primary" id="toggle-currency" type="button" onclick="toggleCurrency()">AUD</button></div><span class="fs-1 text-center d-xl-flex align-items-xl-center"><i class="fa fa-arrows-h"></i></span> <div class="input-group" style="width: fit-content;margin: 10px;"><span class="input-group-text">stWDBRN</span><input class="form-control" type="text" id="stwdbrn-input" name="stwdbrn" oninput="convert('stwdbrn')"></div><script> let currentFiat = 'USD'; function toggleCurrency() { document.getElementById('toggle-currency').textContent = currentFiat; currentFiat = currentFiat === 'USD' ? 'AUD' : 'USD'; document.querySelector('.input-group span').textContent = currentFiat; convert('fiat'); } async function convert(source) { const fiatInput = document.getElementById('fiat-input'); const stwdbrnInput = document.getElementById('stwdbrn-input'); let endpoint = ''; if (source === 'fiat') { const fiatAmount = parseFloat(fiatInput.value); if (isNaN(fiatAmount) || fiatAmount <= 0) { stwdbrnInput.value = ''; return; } endpoint = `/api/v1/${currentFiat.toLowerCase()}/${fiatAmount}`; } else if (source === 'stwdbrn') { const stwdbrnAmount = parseFloat(stwdbrnInput.value); if (isNaN(stwdbrnAmount) || stwdbrnAmount <= 0) { fiatInput.value = ''; return; } endpoint = `/api/v1/token/${stwdbrnAmount}`; } else { return; } try { const response = await fetch(endpoint); if (!response.ok) { throw new Error('Error fetching data'); } const data = await response.json(); if (source === 'fiat') { stwdbrnInput.value = parseFloat(data.stWDBRN).toFixed(2); } else if (source === 'stwdbrn') { fiatInput.value = parseFloat(data[currentFiat.toLowerCase()]).toFixed(2); } } catch (error) { console.error('Failed to fetch conversion data:', error); } } // on window load set the initial value of the input fields convert('fiat'); </script> </div> </div> </div> <div class="bg-circle-1 bg-circle"></div> <div class="bg-circle-2 bg-circle"></div> <div class="bg-circle-3 bg-circle"></div> <div class="bg-circle-4 bg-circle"></div> </header> <section style="margin-top: 50px;margin-bottom: 50px;max-width: 100vw;overflow: hidden;"> <div class="text-center"> <h1 data-bs-toggle="tooltip" data-bss-tooltip="" id="chart-header" title="Toggle per token view" style="border-bottom: 5px dashed var(--bs-body-color);width: fit-content;margin: auto;margin-bottom: 20px;">Current Vault Contents</h1> <div class="text-center" id="data-table"></div> <div class="d-xl-flex justify-content-xl-center" id="pie-chart" style="margin: auto;"><script type="text/javascript"> window.onload = function () { google.charts.load('current', { packages: ['corechart'] }); // Set a callback to run when the API is loaded. google.charts.setOnLoadCallback(drawChart); let isPerTokenView = false; // Track the current view state let chart; // Declare the chart instance globally to reuse it let options = { pieSliceText: 'label', tooltip: { isHtml: false }, legend: { position: 'right', textStyle: { color: 'white' } }, backgroundColor: '#212529', sliceVisibilityThreshold: 0.05 }; async function fetchData(apiUrl) { try { const response = await fetch(apiUrl); if (!response.ok) throw new Error('Failed to fetch data from API'); return await response.json(); } catch (error) { console.error('Error fetching data:', error); return null; } } function transformDataForChart(data) { const chartData = []; for (const token in data) { if (token !== 'total') { chartData.push([ token, // Label for the chart data[token].value, // Value for the chart data[token].tooltip, // Tooltip for the chart ]); } } return chartData; } async function drawChart() { const apiUrl = '/api/v1/vault'; const data = await fetchData(apiUrl); if (!data) return; // Exit if data fetch fails const chartDataTable = new google.visualization.DataTable(); chartDataTable.addColumn('string', 'Token'); chartDataTable.addColumn('number', 'Value'); chartDataTable.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': false } }); chartDataTable.addRows(transformDataForChart(data)); chart = new google.visualization.PieChart(document.getElementById('pie-chart')); resizeAndDraw(chart, chartDataTable, options); updateText(true); populateTable(data); } function updateText(isVault) { let headerText = document.getElementById('chart-header'); if (isVault) { headerText.innerText = 'Current Vault Contents'; } else { headerText.innerText = 'Current Per Token Holding'; } } function populateTable(data) { const tableContainer = document.getElementById('data-table'); tableContainer.innerHTML = ''; // Clear previous table data // Create table elements const table = document.createElement('table'); table.style.margin = 'auto'; table.style.borderCollapse = 'collapse'; table.style.maxWidth = '100%'; const thead = document.createElement('thead'); const tbody = document.createElement('tbody'); // Create table header const headerRow = document.createElement('tr'); ['Name', 'Amount', 'USD Value'].forEach((headerText, index) => { const th = document.createElement('th'); th.textContent = headerText; th.style.border = '1px solid #ccc'; th.style.padding = '8px 20px'; th.style.backgroundColor = '#333'; th.style.color = 'white'; if (headerText === 'USD Value') { th.style.cursor = 'pointer'; // Make it clear this header is clickable th.addEventListener('click', () => sortTableByValue(tbody)); } headerRow.appendChild(th); }); thead.appendChild(headerRow); // Create table rows for (const token in data) { if (token !== 'total') { const row = document.createElement('tr'); const nameCell = document.createElement('td'); nameCell.textContent = data[token].name; nameCell.style.border = '1px solid #ccc'; nameCell.style.padding = '8px 20px'; const amountCell = document.createElement('td'); // If token in other investments otherInvestments = ["Lending"] if (otherInvestments.includes(token)) { amountCell.textContent = `${data[token].amount} Positions`; } else { amountCell.textContent = `${data[token].amount} ${token}`; } amountCell.style.border = '1px solid #ccc'; amountCell.style.padding = '8px 20px'; const valueCell = document.createElement('td'); valueCell.textContent = `$${data[token].value.toFixed(2)}`; valueCell.style.border = '1px solid #ccc'; valueCell.style.padding = '8px 20px'; row.appendChild(nameCell); row.appendChild(amountCell); row.appendChild(valueCell); tbody.appendChild(row); } } table.appendChild(thead); table.appendChild(tbody); tableContainer.appendChild(table); sortTableByValue(tbody); } // Function to sort table rows by the "Value" column function sortTableByValue(tbody) { const rows = Array.from(tbody.querySelectorAll('tr')); const sortedRows = rows.sort((a, b) => { // Remove `$` and parse the value as float const valueA = parseFloat(a.children[2].textContent.replace('$', '')) || 0; const valueB = parseFloat(b.children[2].textContent.replace('$', '')) || 0; return valueA - valueB; }); // Reverse order if already sorted in ascending order const isDescending = tbody.getAttribute('data-sort-order') === 'desc'; if (!isDescending) { sortedRows.reverse(); tbody.setAttribute('data-sort-order', 'desc'); } else { tbody.setAttribute('data-sort-order', 'asc'); } // Append sorted rows back to the tbody tbody.innerHTML = ''; sortedRows.forEach(row => tbody.appendChild(row)); } async function toggleChart() { isPerTokenView = !isPerTokenView; const apiUrl = isPerTokenView ? '/api/v1/token' : '/api/v1/vault'; const data = await fetchData(apiUrl); if (!data) return; // Exit if data fetch fails const chartDataTable = new google.visualization.DataTable(); chartDataTable.addColumn('string', 'Token'); chartDataTable.addColumn('number', 'Value'); chartDataTable.addColumn({ type: 'string', role: 'tooltip', 'p': { 'html': false } }); chartDataTable.addRows(transformDataForChart(data)); resizeAndDraw(chart, chartDataTable, options); updateText(!isPerTokenView); populateTable(data); } function resizeAndDraw(chart, chartDataTable, options) { function resizeChart() { const width = window.innerWidth * 0.8; const height = window.innerHeight * 0.5; const dynamicOptions = { ...options }; if (width >= 700) { dynamicOptions.width = width; dynamicOptions.height = height; } chart.draw(chartDataTable, dynamicOptions); } resizeChart(); window.addEventListener('resize', resizeChart); } // Set button event listener document.getElementById('chart-header').addEventListener('click', toggleChart); }; </script></div><a class="btn btn-secondary" role="button" href="https://www.coingecko.com/en/portfolios/public/vault" target="_blank" style="margin: 10px;">View on Coingecko</a> </div> </section> <section> <div class="container"> <div class="row align-items-center"> <div class="col-lg-6 order-lg-2"> <div class="p-5"><img class="rounded-circle img-fluid" src="assets/img/01.jpg"></div> </div> <div class="col-lg-6 order-lg-1"> <div class="p-5"> <h2 class="display-4">1. Buy tokens</h2> <p>To get started buy stWDBRN tokens at the current price to buy a percent of the vault value. The buy in value is then invested in various projects in order to increase the token's value.<br><br>Send SOL or approved tokens to vault.woodburn.sol and receive stWDBRN in exchange. (Note automated swaps only happen for deposits over 1 USDC)</p><button class="btn btn-primary" id="video-button" type="button" style="margin: 20px;">See how</button><button class="btn btn-primary" id="approved-tokens-button" type="button" style="margin: 20px;">Approved Tokens</button><div id="video-div" style="margin-top: 20px; display: none;"> <video id="video" style="max-width: 75%; height: auto;border-radius: 25px;" controls> <source src="https://cloud.woodburn.au/s/dsaF9MHMz9s5H2q/download/Screen_Recording_20241205_235805_Phantom.mp4" type="video/mp4"> </video> </div> <div id="approved-tokens" style="margin-top: 20px; display: none;"> <h2>Approved Tokens</h2> <p>The following tokens are approved to be used with the stWDBRN Vault.</p> <ul id="approvedTokenList"></ul> </div> <script> function toggleVideo() { var video = document.getElementById("video-div"); var approvedtokens = document.getElementById("approved-tokens"); if (video.style.display === "none") { video.style.display = "block"; approvedtokens.style.display = "none"; } else { video.style.display = "none"; } } async function fetchAndRenderTokens() { try { // Fetch data from the API const response = await fetch('/api/v1/tokens'); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } const tokens = await response.json(); // Select the container where the list will be appended const tokenList = document.getElementById('approvedTokenList'); // Loop through the tokens and create HTML for each tokens.forEach(token => { const listItem = document.createElement('li'); // Create a list item const anchor = document.createElement('a'); // Create a link element anchor.href = token.url; // Set the URL anchor.target = '_blank'; // Open the link in a new tab anchor.textContent = `${token.name} (${token.symbol.toUpperCase()})`; // Set the text listItem.appendChild(anchor); // Append the anchor to the list item tokenList.appendChild(listItem); // Append the list item to the container }); } catch (error) { console.error('Error fetching or rendering tokens:', error); } } function toggleApprovedTokens() { // Fill in the approved tokens list if it isn't already if (document.getElementById("approvedTokenList").innerHTML === "") { fetchAndRenderTokens(); } var approvedtokens = document.getElementById("approved-tokens"); var video = document.getElementById("video-div"); if (approvedtokens.style.display === "none") { approvedtokens.style.display = "block"; video.style.display = "none"; } else { approvedtokens.style.display = "none"; } } document.getElementById("video-button").onclick = function () { toggleVideo(); }; document.getElementById("approved-tokens-button").onclick = function () { toggleApprovedTokens(); }; </script> </div> </div> </div> </div> </section> <section> <div class="container"> <div class="row align-items-center"> <div class="col-lg-6 order-lg-1"> <div class="p-5"><img class="rounded-circle img-fluid" src="assets/img/02.jpg"></div> </div> <div class="col-lg-6 order-lg-2"> <div class="p-5"> <h2 class="display-4">2. HODL and check price</h2> <p>Hodl your stWDBRN while the value fluctuates. Check this site to see the current value.</p> </div> </div> </div> </div> </section> <section> <div class="container"> <div class="row align-items-center"> <div class="col-lg-6 order-lg-2"> <div class="p-5"><img class="rounded-circle img-fluid" src="assets/img/03.jpg"></div> </div> <div class="col-lg-6 order-lg-1"> <div class="p-5"> <h2 class="display-4">3. Sell your tokens</h2> <p>When you want to cash out just sell your tokens back to me at the current token price.</p><a class="btn btn-primary" role="button" href="mailto:vault@woodburn.au" target="_blank">Contact to sell tokens</a> </div> </div> </div> </div> </section> <section> <div class="container"> <div class="p-5"> <h2 class="display-4">Vault addresses</h2> <div class="table-responsive"> <table class="table"> <thead> <tr> <th>Chain</th> <th>Address</th> </tr> </thead> <tbody> <tr> <td>Solana</td> <td><a href="https://explorer.solana.com/address/NWywvhcqdkJsm1s9VVviPm9UfyDtyCW9t8kDb24PDPN" target="_blank">NWywvhcqdkJsm1s9VVviPm9UfyDtyCW9t8kDb24PDPN</a></td> </tr> <tr> <td>Cardano</td> <td><a href="https://cardanoscan.io/stakekey/e12a06f8f40e1b0f06ea9732af8ef7377453e285a163ea7c0d64481be1" target="_blank">stake1uy4qd785pcds7ph2jue2lrhhxa698c5959375lqdv3yphcgwc8qna</a></td> </tr> <tr> <td>Sui</td> <td><a href="https://suivision.xyz/account/0x7e4fa1592e4fad084789f9fe1a4d7631a2e6477b658e777ae95351681bcbe8da" target="_blank">0x7e4fa1592e4fad084789f9fe1a4d7631a2e6477b658e777ae95351681bcbe8da</a></td> </tr> <tr> <td>dYdX</td> <td><a href="https://www.mintscan.io/dydx/address/dydx1ugraczuyfmxy8k38nps4fu7e5derryzx95fs8n" target="_blank">dydx1ugraczuyfmxy8k38nps4fu7e5derryzx95fs8n</a></td> </tr> </tbody> </table> </div> </div> <div class="p-5"> <h2 class="display-4">DeFi Positions</h2> <div class="table-responsive" id="defi-table"> <table class="table"> <thead> <tr> <th>Type</th> <th>Initial Value</th> <th>Date bought</th> <th>Current Value</th> <th>APY</th> <th>Info</th> <th>Last Updated</th> </tr> </thead> <tbody> <tr> <td>Position Name</td> <td>$0</td> <td>00/00/00</td> <td>$0</td> <td>0%</td> <td>Find out more</td> <td>00/00/00</td> </tr> </tbody> </table> </div><script type="text/javascript"> async function fetchAndInjectDefiData() { try { // Fetch data from the API const response = await fetch('/api/v1/defi'); if (!response.ok) { throw new Error('Network response was not ok' + response.statusText); } const data = await response.json(); // Locate the table where the data will be injected const tableBody = document.querySelector('#defi-table tbody'); if (!tableBody) { console.error('Table body not found. Make sure your table has an id "defi-table" with a <tbody> element.'); return; } // Clear existing rows (optional, depending on use case) tableBody.innerHTML = ''; const formatDate = (timestamp) => { const date = new Date(timestamp * 1000); // Convert seconds to milliseconds const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are zero-based const year = String(date.getFullYear()).slice(-2); // Get last two digits of the year return `${day}/${month}/${year}`; }; // Iterate over the data and create table rows data.forEach(item => { const row = document.createElement('tr'); row.innerHTML = ` <td>${item.name}</td> <td>$${item.initial}</td> <td>${formatDate(item.bought)}</td> <td>$${item.value}</td> <td>~${item.apy}%</td> <td><a href="${item.url}" target="_blank">Find out more</a></td> <td>${formatDate(item.updated)}</td> `; tableBody.appendChild(row); }); } catch (error) { console.error('Error fetching or injecting data:', error); } } // Set on page load event listener window.addEventListener('load', fetchAndInjectDefiData); </script> </div> <div class="p-5"> <h2 class="display-4">Other</h2> <div class="table-responsive" id="other-table"> <table class="table"> <thead> <tr> <th>Asset</th> <th>APY</th> <th>Info</th> <th>Last Updated</th> </tr> </thead> <tbody> <tr> <td>Asset Name</td> <td>0%</td> <td>Find out more</td> <td>00/00/00</td> </tr> </tbody> </table> </div><script type="text/javascript"> async function fetchAndInjectDefiData() { try { // Fetch data from the API const response = await fetch('/api/v1/apy'); if (!response.ok) { throw new Error('Network response was not ok' + response.statusText); } const data = await response.json(); // Locate the table where the data will be injected const tableBody = document.querySelector('#other-table tbody'); if (!tableBody) { console.error('Table body not found. Make sure your table has an id "other-table" with a <tbody> element.'); return; } // Clear existing rows (optional, depending on use case) tableBody.innerHTML = ''; const formatDate = (timestamp) => { const date = new Date(timestamp * 1000); // Convert seconds to milliseconds const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are zero-based const year = String(date.getFullYear()).slice(-2); // Get last two digits of the year return `${day}/${month}/${year}`; }; // Iterate over the data and create table rows data.forEach(item => { const row = document.createElement('tr'); row.innerHTML = ` <td>${item.name}</td> <td>~${item.apy}%</td> <td><a href="${item.url}" target="_blank">Find out more</a></td> <td>${formatDate(item.updated)}</td> `; tableBody.appendChild(row); }); } catch (error) { console.error('Error fetching or injecting data:', error); } } // Set on page load event listener window.addEventListener('load', fetchAndInjectDefiData); </script> </div> </div> </section> <footer class="py-5 bg-black"> <div class="container"> <p class="text-center text-white m-0 small">Copyright © <a href="https://nathan.woodburn.au" target="_blank">Nathan.Woodburn/</a> 2024</p> </div> </footer> <script src="assets/bootstrap/js/bootstrap.min.js"></script> <script src="assets/js/bs-init.js"></script> <script src="https://www.gstatic.com/charts/loader.js"></script> </body> </html>