const { apiKey } = props; const addressOptions = [ "Astro Stakers", "Stader (NEARX)", "Linear", "NEAR Crowd", "epic pool", "Aurora (AURORA)", "Tonic", "everstake", "Spin", "sweat", "Custom", ]; State.init({ startDate: "2023-01-01", endDate: "2023-12-31", addresses: [], customAddresses: [], hasSqlError: false, isActivitiesSqlRunning: false, }); /** input functions */ const onAddressSelectValueChange = (value) => { State.update({ addresses: value, }); }; const onCustomAddressesValueChange = ({ target }) => { let customAddresses = target.value.split(","); State.update({ customAddresses, }); }; const onStartDateChange = ({ target }) => { State.update({ startDate: target.value, }); }; const onEndDateChange = ({ target }) => { State.update({ endDate: target.value, }); }; /** sql functions */ const buildSql = () => { let { addresses, customAddresses, startDate, endDate } = state; let shouldIncludeCustomAddresses = addresses.includes("Custom"); if (shouldIncludeCustomAddresses) { addresses = [...addresses, ...customAddresses]; } // remove "Custom" from address list and remove whitespaces addresses = addresses .filter((x) => x != "Custom" && x != "") .map((x) => x.trim()); if (addresses.length == 0) { return ""; } let addressStr = "'%" + addresses.join("%','%") + "%'"; let sql = `with call_contracts as ( select block_timestamp::date as date, project_name, count(method_name) as call_contracts, count( DISTINCT(tx_hash) ) as transactions, count( DISTINCT(signer_id) ) as users from near.core.fact_actions_events_function_call a join near.core.dim_address_labels b on a.receiver_id = b.address where block_timestamp::date >= '${startDate}' and block_timestamp::date <= '${endDate}' and (project_name ilike ANY(${addressStr}) or b.address ilike ANY(${addressStr})) group by 1,2 ), volume as ( select block_timestamp::date as date, c.project_name, sum( deposit / pow(10, 24) ) as near_amount, avg( deposit / pow(10, 24) ) as avg_near_amount, sum( TRANSACTION_FEE / pow(10, 24) ) as gas_amount, avg( TRANSACTION_FEE / pow(10, 24) ) as avg_gas_amount from NEAR.core.fact_transfers a join near.core.dim_address_labels c on a.tx_receiver = c.address where block_timestamp::date >= '${startDate}' and block_timestamp::date <= '${endDate}' and (project_name ilike ANY(${addressStr}) or c.address ilike ANY(${addressStr})) and tx_receiver != 'token.sweat' and STATUS = 'true' group by 1,2 ) select nvl(a.date, b.date) as date, a.project_name, users, transactions, call_contracts, near_amount, avg_near_amount, gas_amount, avg_gas_amount from volume a full outer join call_contracts b on a.project_name = b.project_name and a.date = b.date where a.project_name != 'cex' order by 1 `; return sql; }; const verifyAndRun = (runQuery /* callback */) => { State.update({ hasSqlError: false, }); let sql = buildSql(); if (!sql) { State.update({ hasSqlError: true, }); return; } runQuery(sql); }; /** Main component */ const Component = ({ runQuery, data, hasError, isRunning, isLoading }) => { if (state.isActivitiesSqlRunning != isRunning) { State.update({ isActivitiesSqlRunning: isRunning, }); } if (hasError) { return ( <div className="d-flex flex-column justify-content-center" style={{ height: "60vh" }} > <button onClick={() => { verifyAndRun(runQuery); }} > Try Again </button> <div className="h-100 d-flex align-items-center justify-content-center"> <strong className="text-danger">Error!</strong> </div> </div> ); } if (isRunning) { return ( <button disabled className="btn btn-primary w-100"> <div className="d-flex flex-row justify-content-center align-items-center w-100"> <i className="spinner-grow spinner-grow-sm me-3"></i> <span>Getting Data...</span> </div> </button> ); } if (!data || data.length == 0) { return ( <div className="d-flex flex-column justify-content-center" style={{ height: "60vh" }} > <button onClick={() => { verifyAndRun(runQuery); }} > Search </button> <div className="h-100 d-flex align-items-center justify-content-center"> <strong>No data!</strong> </div> </div> ); } return ( <div className="d-flex flex-column justify-content-center"> <button onClick={() => { verifyAndRun(runQuery); }} > Search </button> {/** Calendar Chart */} <Widget src="kida.near/widget/Untitled-2" props={{ dateColumn: "date", dataColumn: "users", data, legendMax: data.map((x) => x.users).reduce((a, b) => (a > b ? a : b)), legendMin: 0, label: "Number of Unique Wallets", }} /> </div> ); }; /** Page Layout */ const Layout = () => { return ( <> <div className="row"> <div className="col-6 mb-3"> <div className="input-group"> <div className="input-group-prepend"> <span className="input-group-text"> <span>From</span> </span> </div> <input type="date" onChange={onStartDateChange} className="form-control" value={state.startDate} disabled={state.isActivitiesSqlRunning} /> </div> </div> <div className="col-6 mb-3"> <div className="input-group"> <div className="input-group-prepend"> <span className="input-group-text"> <span>To</span> </span> </div> <input type="date" onChange={onEndDateChange} className="form-control" value={state.endDate} disabled={state.isActivitiesSqlRunning} /> </div> </div> <div className="col-12 mb-3"> <div className="input-group"> <div className="input-group-prepend"> <span className="input-group-text"> <span>Addresses</span> </span> </div> <Typeahead options={addressOptions} multiple onChange={onAddressSelectValueChange} placeholder="Choose a project(s).." disabled={state.isActivitiesSqlRunning} /> </div> </div> <div className={`col-12 mb-3 ${ state.addresses.includes("Custom") ? "" : "d-none" }`} > <div className="d-flex flex-column"> <input type="text" className="form-control" onChange={onCustomAddressesValueChange} value={state.customAddresses.join(",")} placeholder="Separate addresses using a comma, eg. Project1,Project2" disabled={state.isActivitiesSqlRunning} /> </div> </div> </div> </> ); }; return ( <div className="p-2"> {/* <iframe className="w-100" style={{ height: "300px" }} srcDoc={content} /> */} <Layout /> {/** Flipside Data Wrapper */} <Widget src="kida.near/widget/Untitled-0" props={{ apiKey: "f1f1b6f1-1bdf-4d19-8c9c-a59dc1642c8b", component: Component, }} /> {state.hasSqlError && ( <strong className="text-danger">Invalid Parameters!</strong> )} </div> );