// State State.init({ theme: "", Dao_Contract: "", Issuer_selected: null, Issuer_filled: "", Receiver: "", ClassIdSelected: "", IssuedAT: "", ExpiresAt: "", Referencelink: "", Referencelink_valid: false, Referencelink_json: false, Referencehash: "", JsonBody: "", IssuerPropList: props.IssuerList, ischeckselected: true, Submitdisable: true, FormIsValid: false, }); //const const MAX_SAFE_INTEGER = 2e53 - 1; const cssFont = fetch( "https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600;700;800" ).body; const css = fetch( "https://raw.githubusercontent.com/dokxo96/fastSbt/master/fastsbt.css?token=GHSAT0AAAAAACEQ4SVRD7BVOYKVKF5B4FEAZF36DWQ" ).body; const httpRequestOpt = { headers: { "x-api-key": props.api_key ? props.api_key : "36f2b87a-7ee6-40d8-80b9-5e68e587a5b5", }, }; if (!cssFont || !css) return ""; if (!state.theme) { State.update({ theme: styled.div` font-family: Manrope, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; ${cssFont} ${css} `, }); } const proposalKinds = { FunctionCall: "call", }; const actions = { AddProposal: "AddProposal", }; const _type = { SHOWINPUT: "showinput", }; //Custom components const Theme = state.theme; const daoId = props.daoId ?? "multi.sputnik-dao.near"; const accountId = props.accountId ?? context.accountId; const NDCicon = "https://emerald-related-swordtail-341.mypinata.cloud/ipfs/QmP5CETfUsGFqdcsnrfPgUk3NvRh78TGZcX2srfCVFuvqi?_gl=1*faq1pt*_ga*Mzc5OTE2NDYyLjE2ODg1MTY4MTA.*_ga_5RMPXG14TE*MTY4OTg3Njc1OC4xMS4xLjE2ODk4NzY4MjYuNjAuMC4w"; const CheckIcon = "https://emerald-related-swordtail-341.mypinata.cloud/ipfs/QmVGE45rLuHiEHh8RPfL11QsQBXVDfmdV3pevZU7CG1ucg?preview=1&_gl=1*1dpaowv*_ga*Mzc5OTE2NDYyLjE2ODg1MTY4MTA.*_ga_5RMPXG14TE*MTY4OTg4MDMyOS4xMi4xLjE2ODk4ODA3MTAuMTkuMC4w"; const SubmitBtn = styled.button` display: flex; width: 107px; padding: 8px 12px; justify-content: center; align-items: center; gap: 10px; color:${state.Submitdisable ? "#606060" : "#000000"}; display: flex; width: 107px; padding: 8px 12px; justify-content: center; align-items: center; gap: 10px; border-radius: 10px; border-width: 1px; border: solid 1px ${state.Submitdisable ? "darkgray" : "transparent"}; background-image: ${ state.Submitdisable ? "linear-gradient(rgba(0, 0, 0,0), rgba(0, 0, 0,0))), radial-gradient(circle at top left,rgba(0, 0, 0,0),rgba(0, 0, 0,0));" : "linear-gradient(#FFD50D, #FFD50D), radial-gradient(circle at top left,#F0E1CE, #F0E1CE);" } background-origin: border-box; background-clip: padding-box, border-box; `; const CustomCheckbox = styled.div` width:20px; height:20px; background:${ state.ischeckselected ? "linear-gradient(90deg, #9333EA 0%, #4F46E5 100%);" : "#F8F8F9;" } border: medium solid ${ state.ischeckselected ? "linear-gradient(90deg, #9333EA 0%, #4F46E5 100%);" : "black" }; border-radius:4px; `; const CardPreview = styled.div` width: 20%; margin: auto 0px !important; @media only screen and (max-width: 480px) { width: 100%; margin: 0; } `; // -- Get all the roles and proposal_bond from the DAO policy const Dao_policy = Near.view(daoId, "get_policy"); const roles = Dao_policy === null ? [] : Dao_policy.roles; const proposal_bond = Dao_policy === null ? [] : Dao_policy.proposal_bond; //Validate if the user can add a Function call to DAO const isUserAllowedTo = (user, kind, action) => { // -- Filter the user roles const userRoles = []; for (const role of roles) { if (role.kind === "Everyone") { userRoles.push(role); continue; } if (!role.kind.Group) continue; if (accountId && role.kind.Group && role.kind.Group.includes(accountId)) { userRoles.push(role); } } // -- Check if the user is allowed to perform the action let allowed = false; userRoles .filter(({ permissions }) => { const allowedRole = permissions.includes(`${kind.toString()}:${action.toString()}`) || permissions.includes(`${kind.toString()}:*`) || permissions.includes(`*:${action.toString()}`) || permissions.includes("*:*"); allowed = allowed || allowedRole; return allowedRole; }) .map((role) => role.name); return allowed; }; const canAddProposal = isUserAllowedTo( accountId, proposalKinds.FunctionCall, actions.AddProposal ); //Get alll daos const daos = Near.view("sputnik-dao.near", "get_dao_list"); const validAccoundAtIssuer = () => { //get the issuer and class const issuer = state.Issuer_selected != "show" ? state.Issuer_selected : state.Issuer_filled; const checkMintersJson = Near.view(issuer, "class_minter", { class: state.ClassIdSelected, }); const mintAuthorities = checkMintersJson.minters; if (!mintAuthorities.includes(context.accountId)) { State.update({ error_msg: "you are no allowed at this issuer", Submitdisable: true, }); return (isValid = false); } }; const validateReceiverHasSbt = () => { const issuer = state.Issuer_selected != _type.SHOWINPUT ? state.Issuer_selected : state.Issuer_filled; const fetchlnk = `https://api.pikespeak.ai/sbt/has-sbt?holder=${ state.Receiver }&class_id=${state.ClassIdSelected}&issuer=${issuer}®istry=${ props.registry ? props.registry : "registry.i-am-human.near" }`; console.log("fetching", fetchlnk); asyncFetch(fetchlnk, httpRequestOpt).then((res) => { console.log("validateReceiverHasSbt", res); if (res.body) { //the receiver already has sbt State.update({ error_msg: "The receiver already has SBT", Submitdisable: false, }); return true; } }); }; //Methods const validatedInputs = async () => { //local methods const regexPattern = /^[a-z0-9_.-]+\.near$/; function validateAccount(inputString) { return regexPattern.test(inputString.toLowerCase()); } const isEmpty = (str) => str.trim() === ""; const showError = (msg) => { return { error_msg: msg, Submitdisable: true, FormIsValid: false, }; }; const showSuccess = () => { return { error_msg: "", Submitdisable: false, FormIsValid: true, }; }; if (isEmpty(state.Dao_Contract)) { //validate the user filled the Dao input const res = showError("Write the DAO contract"); console.log("res", res); return State.update(res); } if (!daos.includes(state.Dao_Contract)) { //validate that the DAO provided is a valid one return State.update(showError("Is not a Dao contract")); } if (isEmpty(state.Issuer_selected)) { //validate the user selected an issuer prefilled return State.update(showError("Select an issuer")); } if ( state.Issuer_selected === _type.SHOWINPUT && isEmpty(state.Issuer_filled) ) { // the user will provide a new issuer return State.update(showError("provide an issuer")); } if (isEmpty(state.Receiver)) { //validate the user filled the Receiver return State.update(showError("Write the receiver")); } if (!validateAccount(state.Receiver)) { //validate the user filled the Receiver return State.update(showError("Receiver is not a valid account")); } if (validateAccount(state.Receiver)) { //validate the user filled the Receiver const _url = context.networkId === "mainnet" ? "https://rpc.mainnet.near.org" : "https://rpc.testnet.near.org"; asyncFetch(_url, { body: `{ "jsonrpc": "2.0", "id": "dontcare", "method": "query", "params": { "request_type": "view_account", "finality": "final", "account_id": "${state.Receiver}" } } `, headers: { "Content-Type": "application/json", }, method: "POST", }).then((res) => { console.log("rest", res); if (res.body.error) { return State.update(showError(res.body.error.data)); } }); } if (state.ischeckselected === true) { if (state.ClassIdSelected === "0") { //validate the user dont add a o in the class id return State.update({ ClassIdSelected: "", error_msg: "Select a token class", Submitdisable: true, FormIsValid: false, }); } if (isEmpty(state.ClassIdSelected)) { //validate the user select a class id higher than 0 return State.update(showError("Select a token class")); } else { if ( (state.Receiver && state.Issuer_selected) || (state.Issuer_selected && state.ClassIdSelected) ) { const issuer = state.Issuer_selected != _type.SHOWINPUT ? state.Issuer_selected : state.Issuer_filled; const fetchlnk = `https://api.pikespeak.ai/sbt/has-sbt?holder=${ state.Receiver }&class_id=${state.ClassIdSelected}&issuer=${issuer}®istry=${ props.registry ? props.registry : "registry.i-am-human.near" }`; console.log("fetching", fetchlnk); asyncFetch(fetchlnk, httpRequestOpt).then((res) => { console.log("validateReceiverHasSbt", res); if (res.body) { //the receiver already has sbt return State.update(showError("The receiver already has SBT")); } }); } //validate alll is good return State.update(showSuccess()); } } return State.update(showSuccess()); }; //split and improve subtmit const createMeta = () => { const meta = { receiver: state.Receiver, metadata: { class: state.ClassIdSelected, }, reference: state.Referencelink || null, reference_hash: state.Referencehash || null, }; return JSON.stringify(meta); }; const encodeArgs = (meta) => { return Buffer.from(meta, "utf-8").toString("base64"); }; const Submitform = () => { const isValid = validatedInputs(); if (isValid) { const meta = createMeta(); const argsencoded = encodeArgs(meta); const { Dao_Contract, Issuer_selected, Issuer_filled } = state; const receiver_id = Issuer_selected === _type.SHOWINPUT ? Issuer_filled : Issuer_selected; Near.call([ { contractName: Dao_Contract, methodName: "add_proposal", args: { proposal: { description: "create proposal to mint SBT$$$$$$$$ProposeCustomFunctionCall", kind: { FunctionCall: { receiver_id, actions: [ { method_name: "sbt_mint", args: argsencoded, deposit: "80000000000000000000000", gas: "150000000000000", }, ], }, }, }, }, deposit: 100000000000000000000000, gas: "150000000000000", }, ]); } else { console.log("not valid"); } }; const validateReference = (link) => { try { if (state.Referencelink.length > 0) { //fetch the link asyncFetch(state.Referencelink).then((response) => { console.log(response); //validate if its a JSON if ( response.contentType.trim() === "application/json" && response.body != undefined ) { // convert to Uft8 the body content const toUtf8 = ethers.utils.toUtf8Bytes( JSON.stringify(response.body) ); //Encrypt the Uft8 string into Sha256 const encryptSha256 = ethers.utils.keccak256(toUtf8); //parse the sha256 into a base64 string let bodyEncoded = Buffer.from(encryptSha256, "utf-8").toString( "base64" ); //modify the state and mark the Rererence as a valid json and fill the reference hash with the // Base64(sha256(bodycontent)) State.update({ Referencelink_valid: true, Referencelink_json: true, Referencehash: bodyEncoded, //previe the body content JsonBody: JSON.stringify(response.body), }); } else { State.update({ Referencelink_valid: false, Referencelink_json: false, Referencehash: "", JsonBody: "", }); } }); } } catch (error) { console.log(error); State.update({ Referencelink_valid: false, Referencelink_json: false, }); } }; const handleDao_Contract = (e) => { console.log("padre", e); State.update({ Dao_Contract: e }); //props.validatedInputs(); }; const handleIssuer_selected = (e) => { console.log("padre", e); State.update({ Issuer_filled: e }); //props.validatedInputs(); }; const handleReceiver = (e) => { console.log("padre", e); State.update({ Receiver: e.target.value }); //props.validatedInputs(); }; return ( <Theme> <div class="Rowcont"> <Widget src={`dokxo.near/widget/FastSBT_Input`} props={{ title: "Minter DAO", placeholder: "Input DAO contract address", value: state.Dao_Contract, onchangeFunc: handleDao_Contract, }} /> <div class="Colcont"> <h1 class="H1styled">Issuer </h1> <select class="Dropdown" placeholder="Input DAO contract address " value={state.Issuer_selected} onChange={(e) => { State.update({ Issuer_selected: e.target.value }); validatedInputs(); }} > <option default value=""> Select issuer </option> <option default value="registry.i-am-human.near"> registry.i-am-human.near </option> <option default value="registry-v1.gwg-testing.near"> registry-v1.gwg-testing.near </option> <option default value="fractal.i-am-human.near"> fractal.i-am-human.near </option> <option default value="issuer.regens.near"> issuer.regens.near </option> <option default value="issuer.proofofvibes.near"> issuer.proofofvibes.near </option> {props.IssuerList ? ( props.IssuerList.map((item) => { return <option value={item.value}> {item.label}</option>; }) ) : ( <></> )} <option value={_type.SHOWINPUT}>Other -- write it.</option> </select> </div> {state.Issuer_selected === _type.SHOWINPUT ? ( <Widget src={`dokxo.near/widget/FastSBT_Input`} props={{ title: "Enter issuer", placeholder: "Input Issuer", value: state.Issuer_filled, onchangeFunc: handleIssuer_selected, }} /> ) : ( <></> )} <Widget src={`dokxo.near/widget/FastSBT_Input`} props={{ title: "Receiver", placeholder: "dokxo.near", value: state.Receiver, onchangeFunc: handleReceiver, }} /> </div> </Theme> );