From 21f6d633144e22b7b42eee941f61ab1773f57aa8 Mon Sep 17 00:00:00 2001 From: Owen Kellogg Date: Sat, 16 Sep 2023 09:42:26 +0200 Subject: [PATCH 01/13] ContractOperator class and Meeting example --- pages/calendar/[txid].tsx | 27 +++++++++++++ services/calendar_event_operator.ts | 63 +++++++++++++++++++++++++++++ services/contract_operator.ts | 41 +++++++++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 services/calendar_event_operator.ts create mode 100644 services/contract_operator.ts diff --git a/pages/calendar/[txid].tsx b/pages/calendar/[txid].tsx index e4d8b3e..0f07065 100644 --- a/pages/calendar/[txid].tsx +++ b/pages/calendar/[txid].tsx @@ -21,6 +21,7 @@ import { useRouter } from "next/router"; import { Meeting } from "../../src/contracts/meeting"; import artifact from "../../artifacts/meeting.json"; import { fetchTransaction } from "../../services/whatsonchain"; +import { CalendarEventOperator } from "../../services/calendar_event_operator"; Meeting.loadArtifact(artifact); @@ -114,10 +115,36 @@ const NewCalendarEventPage = () => { const [requireInvites, setRequireInvites] = useState(false); const isContractReady = useMemo(() => eventTitle.length > 0, [eventTitle]); + const [contractOperator, setContractOperator] = useState(null); + const wallet = useWallet(); const router = useRouter(); + useEffect(() => { + + CalendarEventOperator.load({ origin, signer }).then((operator) => { + + setContractOperator(operator); + }); + + }, []); + + useEffect(() => { + if (contractOperator) { + console.log("contractOperator loaded", contractOperator); + } + }, [contractOperator]); + + async function attend() { + + if (!contractOperator) return; + + const tx = await contractOperator.attend(); + + console.log("attending!", tx); + } + useEffect(() => { if (!router.query.txid) return; diff --git a/services/calendar_event_operator.ts b/services/calendar_event_operator.ts new file mode 100644 index 0000000..2e13dbe --- /dev/null +++ b/services/calendar_event_operator.ts @@ -0,0 +1,63 @@ + +import { MethodCallOptions, Signer, findSig } from 'scrypt-ts'; +import { Meeting } from '../src/contracts/meeting'; + +import ContractOperator from "./contract_operator"; +import { fetchTransaction } from './whatsonchain'; + + +export class CalendarEventOperator extends ContractOperator { + + static async load({ origin, signer }: { origin: string, signer: Signer }): Promise { + + // load record from server to get current location and pre-image of hashed props + + const { location } = await ContractOperator.loadRecord({ origin }); + + // load current location transaction from blockchain + + const tx = await fetchTransaction({ txid: location.split('_')[0] }); + + const contract = Meeting.fromTx(tx, 0); + + return new CalendarEventOperator({ origin, contract, signer }); + } + + async attend(): Promise { + + await this.contract.connect(this.signer); + + const nextInstance = this.contract.next(); + + const publicKey = await this.signer.getDefaultPubKey(); + + const { tx } = await this.contract.methods.attend((sigResponses: any) => { + return findSig(sigResponses, publicKey) + }, { + pubKeyOrAddrToSign: publicKey, + next: { + instance: nextInstance, + balance: this.contract.balance + }, + } as MethodCallOptions); + + this.contract = Meeting.fromTx(tx, 0); + + await this.contract.connect(signer); + + return this; + } + + async invite({ pubkey }: { pubkey: string }): Promise { + + } + + async cancel(): Promise { + + } + + async decline(): Promise { + + } + +} \ No newline at end of file diff --git a/services/contract_operator.ts b/services/contract_operator.ts new file mode 100644 index 0000000..bcf4e6f --- /dev/null +++ b/services/contract_operator.ts @@ -0,0 +1,41 @@ +import axios from "axios"; +import { Signer } from "scrypt-ts"; + +export default class ContractOperator { + + contract: T; + + origin: string; + + signer: Signer; + + constructor({ origin, signer, contract }: { origin: string, signer: Signer, contract: T }) { + this.origin = origin; + this.signer = signer; + this.contract = contract; + } + + /*async handleContractUpdated({ method, params, outpoint }: { method: string, params: any, outpoint: string }): Promise { + + // post update to the server to be stored in the database + }*/ + + + static async loadRecord({ origin }: { origin: string }): Promise { + + console.log('load record', { origin }); + + // load record from server to get current location and pre-image of hashed props + + const { data } = await axios.get(`https://pow.co/api/v1/contracts/${origin}`); + + return data as ContractRecord; + } + +} + +export interface ContractRecord { + origin: string; + location: string; + props: any; +} From 2ce097a5ac203924ebd73a0af1a67bc33fd61c1e Mon Sep 17 00:00:00 2001 From: Guillaume De Martino Date: Sat, 16 Sep 2023 10:58:22 +0200 Subject: [PATCH 02/13] WIP attend meeting --- components/v13_EventDetail.tsx | 46 ++++++++++++++-- components/v13_SimpleEventCard.tsx | 84 ++++++++++++++++++----------- pages/v13_calendar/index.tsx | 3 +- services/calendar_event_operator.ts | 46 +++++++++++++--- services/contract_operator.ts | 4 +- 5 files changed, 141 insertions(+), 42 deletions(-) diff --git a/components/v13_EventDetail.tsx b/components/v13_EventDetail.tsx index 662c811..121d51f 100644 --- a/components/v13_EventDetail.tsx +++ b/components/v13_EventDetail.tsx @@ -1,18 +1,42 @@ 'use client' -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import Link from 'next/link'; import { Meeting } from '../services/meetings' import Drawer from './v13_Drawer'; import BoostPopup from './v13_BoostpowButton/BoostPopup'; +import { CalendarEventOperator } from '../services/calendar_event_operator'; +import useWallet from '../hooks/v13_useWallet'; + +import { Meeting as MeetingContract } from "../src/contracts/meeting" +import artifact from "../artifacts/meeting.json"; + +MeetingContract.loadArtifact(artifact); interface EventDetailCardProps { meeting: Meeting; } const EventDetailCard = ({meeting}:EventDetailCardProps) => { const [boostPopupOpen, setBoostPopupOpen] = useState(false) + const [contractOperator, setContractOperator] = useState(null) + const wallet = useWallet() + const ticketPrice = 0; + + useEffect(() => { + CalendarEventOperator.load({ origin: meeting.origin, signer: wallet!.signer }).then(setContractOperator) + },[wallet]) + + useEffect(() => { + console.log("contract operator loaded", contractOperator) + },[]) + + const attend = async () => { + const opResponse = await contractOperator?.attend() + console.log(opResponse) + } const handleBuy = (e:any) => { e.preventDefault() + attend() } const handleBoost = (e:any) => { @@ -107,7 +131,23 @@ const EventDetailCard = ({meeting}:EventDetailCardProps) => { )} */} - + ) + : ( + )} + ) diff --git a/pages/v13_calendar/index.tsx b/pages/v13_calendar/index.tsx index 1205889..966da7a 100644 --- a/pages/v13_calendar/index.tsx +++ b/pages/v13_calendar/index.tsx @@ -12,12 +12,13 @@ import { ScryptRanking } from "../issues"; import { getMeeting } from "../../services/meetings"; import SimpleEventCard from "../../components/v13_SimpleEventCard"; import Link from "next/link"; +import { CalendarEventOperator } from "../../services/calendar_event_operator"; const RankedMeetingCard = ({origin, totaldifficulty}: ScryptRanking) => { const [cardLoading, setCardLoading] = useState(true) const [meeting, setMeeting] = useState(null) - const wallet = useWallet() + const getMeetingData = () => { getMeeting({txid: origin}).then((data) => { diff --git a/services/calendar_event_operator.ts b/services/calendar_event_operator.ts index 2e13dbe..b92098e 100644 --- a/services/calendar_event_operator.ts +++ b/services/calendar_event_operator.ts @@ -1,10 +1,13 @@ -import { MethodCallOptions, Signer, findSig } from 'scrypt-ts'; +import { HashedSet, MethodCallOptions, PubKey, Signer, findSig, hash160 } from 'scrypt-ts'; import { Meeting } from '../src/contracts/meeting'; import ContractOperator from "./contract_operator"; import { fetchTransaction } from './whatsonchain'; +import { Meeting as MeetingContract } from "../src/contracts/meeting" +import artifact from "../artifacts/meeting.json"; +MeetingContract.loadArtifact(artifact); export class CalendarEventOperator extends ContractOperator { @@ -12,13 +15,42 @@ export class CalendarEventOperator extends ContractOperator { // load record from server to get current location and pre-image of hashed props - const { location } = await ContractOperator.loadRecord({ origin }); + const { location, props } = await ContractOperator.loadRecord({ origin }); // load current location transaction from blockchain - const tx = await fetchTransaction({ txid: location.split('_')[0] }); - - const contract = Meeting.fromTx(tx, 0); + //const tx = await fetchTransaction({ txid: location.split('_')[0] }); + //temporary hack + const tx = await fetchTransaction({ txid: origin}); + + // initialize all HashSet / HashedMap props + const invitees = new HashedSet() + const attendees = new HashedSet() + + // fill values based on props (pre-images) from server + if (props.invitees){ + props.invitees.forEach((str: string) => { + invitees.add(PubKey(str)) + }); + } + if (props.attendees){ + props.attendees.forEach((str: string) => { + attendees.add(PubKey(str)) + }); + } + + let contract + try { + contract = Meeting.fromTx(tx, 0, { + invitees, + attendees + }); + + console.log("contract",contract) + + } catch (error) { + throw error + } return new CalendarEventOperator({ origin, contract, signer }); } @@ -31,7 +63,7 @@ export class CalendarEventOperator extends ContractOperator { const publicKey = await this.signer.getDefaultPubKey(); - const { tx } = await this.contract.methods.attend((sigResponses: any) => { + const { tx } = await this.contract.methods.attend(PubKey(publicKey.toString()), (sigResponses: any) => { return findSig(sigResponses, publicKey) }, { pubKeyOrAddrToSign: publicKey, @@ -43,7 +75,7 @@ export class CalendarEventOperator extends ContractOperator { this.contract = Meeting.fromTx(tx, 0); - await this.contract.connect(signer); + await this.contract.connect(this.signer); return this; } diff --git a/services/contract_operator.ts b/services/contract_operator.ts index bcf4e6f..5c09c52 100644 --- a/services/contract_operator.ts +++ b/services/contract_operator.ts @@ -29,7 +29,9 @@ export default class ContractOperator { const { data } = await axios.get(`https://pow.co/api/v1/contracts/${origin}`); - return data as ContractRecord; + const contract = data.contract + + return contract as ContractRecord; } } From 507726f0c5d06ec1df66623e6f806369e7cd805e Mon Sep 17 00:00:00 2001 From: Guillaume De Martino Date: Sat, 16 Sep 2023 15:48:03 +0200 Subject: [PATCH 03/13] create meeting using scrypt not API --- pages/v13_calendar/index.tsx | 82 +++++++++++++++++++++--------------- pages/v13_calendar/new.tsx | 74 +++++++++++++++++--------------- 2 files changed, 88 insertions(+), 68 deletions(-) diff --git a/pages/v13_calendar/index.tsx b/pages/v13_calendar/index.tsx index 966da7a..66180e3 100644 --- a/pages/v13_calendar/index.tsx +++ b/pages/v13_calendar/index.tsx @@ -3,7 +3,8 @@ import React, { useEffect, useState } from "react"; import ThreeColumnLayout from "../../components/v13_ThreeColumnLayout"; import NewEventForm, { NewEvent } from "../../components/v13_NewEventForm"; import axios from "axios"; -import { bsv } from "scrypt-ts"; +import toast from "react-hot-toast" +import { HashedSet, PubKey, bsv, toByteString } from "scrypt-ts"; import useWallet from "../../hooks/v13_useWallet"; import { useRouter } from "next/navigation"; import Loader from "../../components/Loader"; @@ -13,6 +14,10 @@ import { getMeeting } from "../../services/meetings"; import SimpleEventCard from "../../components/v13_SimpleEventCard"; import Link from "next/link"; import { CalendarEventOperator } from "../../services/calendar_event_operator"; +import { Meeting } from "@/contracts/meeting"; +import artifact from "../../artifacts/meeting.json" + +Meeting.loadArtifact(artifact) const RankedMeetingCard = ({origin, totaldifficulty}: ScryptRanking) => { @@ -98,39 +103,48 @@ const CalendarPage = () => { const { rankings } = data || [] const handleSubmitEvent = async (newEvent: NewEvent) => { - try { - - const { data } = await axios.post(`https://pow.co/api/v1/meetings/new`, { - title: newEvent.title, - description: newEvent.description, - start: newEvent.start, - end: newEvent.end, - owner: newEvent.owner, - organizer: newEvent.organizer, - url: newEvent.url, - status: newEvent.status, - location: newEvent.location, - inviteRequired: newEvent.inviteRequired, - }) - - const script = bsv.Script.fromASM(data.scriptASM) - - const tx = await wallet?.createTransaction({ - outputs: [ - new bsv.Transaction.Output({ - script, - satoshis: 10 - }) - ] - }) - - if (!tx) { return } - - console.log('meeting.created', tx.hash) - - router.push(`/events/${tx.hash}`) - - console.log('meeting.new', data) + + if (!wallet) { + + toast("Error No Wallet Connected", { + icon: "📛", + style: { + borderRadius: "10px", + background: "#333", + color: "#fff", + }, + }); + + return + + } + + console.log("submiting new event", newEvent) + try { + + const meeting = new Meeting( + toByteString(newEvent.title, true), + toByteString(newEvent.description!, true), + BigInt(newEvent.start), + BigInt(newEvent.end!), + toByteString(newEvent.location!, true), + toByteString(newEvent.url!, true), + toByteString(newEvent.status!, true), + PubKey(toByteString(wallet!.publicKey!.toString())), + new HashedSet(), + new HashedSet(), + newEvent.inviteRequired + ) + + await meeting.connect(wallet.signer) + + const result = await meeting.deploy(10) + + console.log(result) + + console.log('meeting.created', result.hash) + + router.push(`/events/${result.hash}`) } catch(error) { diff --git a/pages/v13_calendar/new.tsx b/pages/v13_calendar/new.tsx index 70da012..6c8cd47 100644 --- a/pages/v13_calendar/new.tsx +++ b/pages/v13_calendar/new.tsx @@ -6,8 +6,8 @@ import UserIcon from '../../components/UserIcon' import Datepicker from "tailwind-datepicker-react" import { useDropzone } from 'react-dropzone'; import { buf2hex } from '../../utils/file' - -import { SmartContract, Scrypt, bsv } from "scrypt-ts"; +import toast from "react-hot-toast" +import { SmartContract, Scrypt, bsv, toByteString, PubKey, HashedSet } from "scrypt-ts"; import axios from "axios"; import useWallet from "../../hooks/v13_useWallet"; import { useRouter } from "next/navigation"; @@ -31,39 +31,45 @@ const NewCalendarEventPage = () => { const wallet = useWallet() const handleSubmitEvent = async (newEvent: NewEvent) => { + if (!wallet) { + + toast("Error No Wallet Connected", { + icon: "📛", + style: { + borderRadius: "10px", + background: "#333", + color: "#fff", + }, + }); + + return + + } try { - - const { data } = await axios.post(`https://pow.co/api/v1/meetings/new`, { - title: newEvent.title, - description: newEvent.description, - start: newEvent.start, - end: newEvent.end, - owner: newEvent.owner, - organizer: newEvent.organizer, - url: newEvent.url, - status: newEvent.status, - location: newEvent.location, - inviteRequired: newEvent.inviteRequired, - }) - - const script = bsv.Script.fromASM(data.scriptASM) - - const tx = await wallet?.createTransaction({ - outputs: [ - new bsv.Transaction.Output({ - script, - satoshis: 10 - }) - ] - }) - - if (!tx) { return } - - console.log('meeting.created', tx.hash) - - router.push(`/events/${tx.hash}`) - - console.log('meeting.new', data) + + const meeting = new Meeting( + toByteString(newEvent.title, true), + toByteString(newEvent.description!, true), + BigInt(newEvent.start), + BigInt(newEvent.end!), + toByteString(newEvent.location!, true), + toByteString(newEvent.url!, true), + toByteString(newEvent.status!, true), + PubKey(toByteString(wallet!.publicKey!.toString())), + new HashedSet(), + new HashedSet(), + newEvent.inviteRequired + ) + + await meeting.connect(wallet.signer) + + const result = await meeting.deploy(10) + + console.log(result) + + console.log('meeting.created', result.hash) + + router.push(`/events/${result.hash}`) } catch(error) { From eef8c4716b7f86ffbaa930d928cbdc737922e925 Mon Sep 17 00:00:00 2001 From: Guillaume De Martino Date: Sat, 16 Sep 2023 15:57:33 +0200 Subject: [PATCH 04/13] fix build error in calendar_event_operator --- services/calendar_event_operator.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/services/calendar_event_operator.ts b/services/calendar_event_operator.ts index b92098e..63ee047 100644 --- a/services/calendar_event_operator.ts +++ b/services/calendar_event_operator.ts @@ -80,16 +80,20 @@ export class CalendarEventOperator extends ContractOperator { return this; } - async invite({ pubkey }: { pubkey: string }): Promise { + async invite({ pubkey }: { pubkey: string }): Promise { + + return null } - async cancel(): Promise { + async cancel(): Promise { + return null } - async decline(): Promise { + async decline(): Promise { + return null } } \ No newline at end of file From 1d781d6e4ce5bda520e210c83fc9680783f32fb1 Mon Sep 17 00:00:00 2001 From: Guillaume De Martino Date: Wed, 20 Sep 2023 23:23:36 +0200 Subject: [PATCH 05/13] fix IssueCard display error --- components/IssueCard.tsx | 67 ++++++++++++++++++++++++++++------------ pages/issues/index.tsx | 13 +++++--- services/issues.ts | 6 ++-- 3 files changed, 59 insertions(+), 27 deletions(-) diff --git a/components/IssueCard.tsx b/components/IssueCard.tsx index e61a4c1..33f7993 100644 --- a/components/IssueCard.tsx +++ b/components/IssueCard.tsx @@ -9,6 +9,11 @@ import { TxOutputRef } from 'scrypt-ts'; import axios from 'axios'; import { parse } from 'path'; +import artifact from "../artifacts/issue.json"; +import Link from 'next/link'; + +Issue.loadArtifact(artifact) + export interface Signer { // Define the properties of the Signer interface as per your requirement @@ -32,6 +37,7 @@ interface Comment { interface IssueCardProps { issue: Issue; + contractLocation: string; onAddBounty: (issue: Issue) => Promise; onLeaveComment: (issue: Issue) => Promise; onMarkAsComplete: (issue: Issue) => Promise; @@ -42,6 +48,7 @@ interface IssueCardProps { const IssueCard: React.FC = (props: { issue: Issue, + contractLocation: string, onAddBounty: (issue: Issue) => Promise, onLeaveComment: (issue: Issue) => Promise, onMarkAsComplete: (issue: Issue) => Promise, @@ -53,10 +60,10 @@ const IssueCard: React.FC = (props: { const [addingBounty, setAddingBounty] = useState(false); const [satoshis, setSatoshis] = useState(null); - const [newBounty, setNewBounty] = useState(BigInt(props.issue.balance -1)); + const [newBounty, setNewBounty] = useState(0n); const [issue, setIssue] = useState(props.issue); - const [location, setLocation] = useState((props.issue.from as TxOutputRef)?.tx?.hash); - const [origin, setOrigin] = useState(null); + const [location, setLocation] = useState(props.contractLocation); + const [origin, setOrigin] = useState(props.origin); const [completionStatus, setCompletionStatus] = useState<'incomplete' | 'posting' | 'complete'>('incomplete'); const [commentBoxVisible, setCommentBoxVisible] = useState(false); const [newComment, setNewComment] = useState(''); @@ -66,6 +73,21 @@ const IssueCard: React.FC = (props: { const [assignPopupVisible, setAssignPopupVisible] = useState(false); const [publicKeyInput, setPublicKeyInput] = useState(''); + useEffect(() => { + + if(location){ + + let [txid, vout] = location.split('_') + axios.get(`https://api.whatsonchain.com/v1/bsv/main/tx/hash/${txid}`).then((resp) => { + console.log("location tx", resp.data.vout[vout]) + let amount = Math.round(resp.data.vout[vout].value * 1e8) + setNewBounty(BigInt(amount) - 1n) + }) + + } + + },[location]) + const handleAssignButtonClick = () => { setAssignPopupVisible(true); }; @@ -105,7 +127,10 @@ const IssueCard: React.FC = (props: { }); console.log({ result }) setIssue(newIssue); - setNewBounty(BigInt(newIssue.balance - 1)); + let [txid,vout] = result.data.contract.location.split('_') + let newLocTx = await axios.get(`https://api.whatsonchain.com/v1/bsv/main/tx/hash/${txid}`) + let amount = Math.round(newLocTx.data.vout[vout].value * 1e8) + setNewBounty(BigInt(amount)- 1n) props.refresh(); setAssignSuccess(true); @@ -187,7 +212,10 @@ useEffect(() => { const result = await axios.get(`https://pow.co/api/v1/issues/${(newIssue.from as TxOutputRef)?.tx?.hash}`); console.log({ result }) setIssue(newIssue); - setNewBounty(BigInt(newIssue.balance - 1)); + let [txid,vout] = result.data.contract.location.split('_') + let newLocTx = await axios.get(`https://api.whatsonchain.com/v1/bsv/main/tx/hash/${txid}`) + let amount = Math.round(newLocTx.data.vout[vout].value * 1e8) + setNewBounty(BigInt(amount)- 1n) props.refresh(); }; @@ -230,7 +258,10 @@ useEffect(() => { const result = await axios.get(`https://pow.co/api/v1/issues/${tx.hash}`); console.log({ result }) setIssue(newIssue); - setNewBounty(BigInt(newIssue.balance - 1)); + let [txid,vout] = result.data.contract.location.split('_') + let newLocTx = await axios.get(`https://api.whatsonchain.com/v1/bsv/main/tx/hash/${txid}`) + let amount = Math.round(newLocTx.data.vout[vout].value * 1e8) + setNewBounty(BigInt(amount)- 1n) props.refresh(); // Assuming onLeaveComment is a function that posts the comment and returns the JSON response @@ -245,21 +276,18 @@ useEffect(() => { } }; - const title = Buffer.from(issue.title, 'hex').toString('utf8'); - const description = Buffer.from(issue.description, 'hex').toString('utf8'); - const organization = Buffer.from(issue.organization, 'hex').toString('utf8'); - const repo = Buffer.from(issue.repo, 'hex').toString('utf8'); - - console.log('ORIGIN', props.origin) return ( -
-

{title}

-

{organization}/{repo}

-

{description}

-

Bounty: {newBounty.toString()}

-

Location: {props.origin.location}

-

Origin: {props.origin.origin}

+
+ +

{issue.title}

+ +
+

{issue.organization}/{issue.repo}

+

{issue.description}

+

Bounty: {newBounty.toString()} sats

+

Location: {location}

+

Origin: {origin}

@@ -351,6 +379,7 @@ useEffect(() => { ))} +
); diff --git a/pages/issues/index.tsx b/pages/issues/index.tsx index 9cc48de..476e007 100644 --- a/pages/issues/index.tsx +++ b/pages/issues/index.tsx @@ -19,18 +19,21 @@ export interface ScryptRanking { const RankedIssueCard = ({origin, totaldifficulty}: ScryptRanking) => { const [issue, setIssue] = useState(null) + const [location, setLocation] = useState(null) const wallet = useWallet() const getIssueData = () => { - getIssue({txid: origin}).then((data) => { - console.log(data) - setIssue(data) + getIssue({txid: origin.split('_')[0]}).then((data) => { + console.log("issue data", data) + setIssue(data.props) + setLocation(data.location) }) } useEffect(() => { - getIssueData() + console.log(origin) + getIssueData() },[origin]) const handleRefresh = () => { @@ -54,7 +57,7 @@ const RankedIssueCard = ({origin, totaldifficulty}: ScryptRanking) => { } return (
- + {issue ? : "loading"}
) } diff --git a/services/issues.ts b/services/issues.ts index 4da6bf6..57fce33 100644 --- a/services/issues.ts +++ b/services/issues.ts @@ -243,13 +243,13 @@ export interface NewIssue { return issue; } - export async function getIssue({ txid }: {txid: string}): Promise { + export async function getIssue({ txid }: {txid: string}): Promise { try { const { data } = await axios.get(`https://pow.co/api/v1/issues/${txid}`) - - return data.issue as Issue + console.log(data) + return data.origin } catch(error) { From 26e5e32e2e2fb66a0e2471e73bfae7ec717aef44 Mon Sep 17 00:00:00 2001 From: Guillaume De Martino Date: Wed, 20 Sep 2023 23:56:07 +0200 Subject: [PATCH 06/13] create issue operator --- services/calendar_event_operator.ts | 3 +- services/issue_operator.ts | 131 ++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 services/issue_operator.ts diff --git a/services/calendar_event_operator.ts b/services/calendar_event_operator.ts index 63ee047..52cc59b 100644 --- a/services/calendar_event_operator.ts +++ b/services/calendar_event_operator.ts @@ -4,10 +4,9 @@ import { Meeting } from '../src/contracts/meeting'; import ContractOperator from "./contract_operator"; import { fetchTransaction } from './whatsonchain'; -import { Meeting as MeetingContract } from "../src/contracts/meeting" import artifact from "../artifacts/meeting.json"; -MeetingContract.loadArtifact(artifact); +Meeting.loadArtifact(artifact); export class CalendarEventOperator extends ContractOperator { diff --git a/services/issue_operator.ts b/services/issue_operator.ts new file mode 100644 index 0000000..22ade96 --- /dev/null +++ b/services/issue_operator.ts @@ -0,0 +1,131 @@ +import { ContractTransaction, HashedSet, MethodCallOptions, PubKey, Signer, bsv, findSig, hash160 } from 'scrypt-ts'; +import { Issue } from "../src/contracts/issue"; + +import ContractOperator from "./contract_operator"; +import { fetchTransaction } from "./whatsonchain"; + +import artifact from '../artifacts/issue.json' + +Issue.loadArtifact(artifact) + +export class IssueOperator extends ContractOperator { + + static async load({ origin, signer }: { origin: string, signer: Signer }): Promise { + + // load record from server to get current location and pre-image of hashed props + + const { location, props } = await ContractOperator.loadRecord({ origin }); + + // load current location transaction from blockchain + + //const tx = await fetchTransaction({ txid: location.split('_')[0] }); + //temporary hack + const tx = await fetchTransaction({ txid: origin}); + + // initialize all HashSet / HashedMap props + const invitees = new HashedSet() + const attendees = new HashedSet() + + // fill values based on props (pre-images) from server + if (props.invitees){ + props.invitees.forEach((str: string) => { + invitees.add(PubKey(str)) + }); + } + if (props.attendees){ + props.attendees.forEach((str: string) => { + attendees.add(PubKey(str)) + }); + } + + let contract + try { + contract = Issue.fromTx(tx, 0, { + invitees, + attendees + }); + + console.log("contract",contract) + + } catch (error) { + throw error + } + + return new IssueOperator({ origin, contract, signer }); + } + + async close(): Promise { + + return null + + } + + async complete(): Promise { + + return null + + } + + async assign(): Promise { + + return null + + } + + async addBounty(satoshis: bigint): Promise { + + this.contract.bindTxBuilder('addBounty', async ( + current: Issue, + options: MethodCallOptions + ): Promise => { + + const nextInstance = current.next() + + const newBalance = current.balance + Number(satoshis) + + const tx = new bsv.Transaction() + tx.addInput(current.buildContractInput(options.fromUTXO)).addOutput( + new bsv.Transaction.Output({ + script: nextInstance.lockingScript, + satoshis: newBalance + }) + ) + const changeAddress = await this.signer.getDefaultAddress() + tx.change(changeAddress) + + return Promise.resolve({ + tx, + atInputIndex: 0, + nexts: [ + { + instance: nextInstance, + balance: newBalance, + atOutputIndex: 0 + } + ] + }) + }) + + const { tx } = await this.contract.methods.addBounty(satoshis) + + this.contract = Issue.fromTx(tx,0) + + return this + + + + } + + async addComment(): Promise { + + return null + + } + + async reopen(): Promise { + + return null + + } + +} \ No newline at end of file From 162d89993a5fa320b6b2bfee4961241ef4a5dcf8 Mon Sep 17 00:00:00 2001 From: Guillaume De Martino Date: Thu, 21 Sep 2023 00:08:09 +0200 Subject: [PATCH 07/13] fix compile error --- pages/issues/[txid].tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/pages/issues/[txid].tsx b/pages/issues/[txid].tsx index f6abe02..8c7e82e 100644 --- a/pages/issues/[txid].tsx +++ b/pages/issues/[txid].tsx @@ -106,6 +106,7 @@ const IssuePage = () => { onMarkAsComplete={onMarkAsComplete} refresh={refresh} origin={origin} + contractLocation={origin} methodCalls={methodCalls} /> From 2acbe8fdce87decbb35cdc8a5261d707305fdc99 Mon Sep 17 00:00:00 2001 From: Guillaume De Martino Date: Fri, 22 Sep 2023 11:30:49 +0200 Subject: [PATCH 08/13] more fixes to new post detail page --- app/t/[txid]/page.tsx | 47 ++- components/PostDetailCard.tsx | 13 +- components/v13_BoostContentCardV2.tsx | 39 ++- components/v13_LoveOrdButton.tsx | 155 +++++++++ components/v13_LoveOrdPopup.tsx | 173 +++++++++ components/v13_WalletProviderPopUp.tsx | 462 +++++++++++++++++++++++++ 6 files changed, 849 insertions(+), 40 deletions(-) create mode 100644 components/v13_LoveOrdButton.tsx create mode 100644 components/v13_LoveOrdPopup.tsx create mode 100644 components/v13_WalletProviderPopUp.tsx diff --git a/app/t/[txid]/page.tsx b/app/t/[txid]/page.tsx index 3cdfee4..8dfe636 100644 --- a/app/t/[txid]/page.tsx +++ b/app/t/[txid]/page.tsx @@ -55,6 +55,7 @@ interface Player { interface BitcoinFile { contentType: string; content: string; + txid?: string; } interface BoostTag { @@ -84,23 +85,33 @@ export interface TransactionDetails { } async function getTransactionDetails(txid: string): Promise { + const [twetchResult, contentResponse, repliesResponse, onchainData] = await Promise.all([ twetchDetailQuery(txid).catch((err) => console.log(err)), - axios.get(`https://pow.co/api/v1/content/${txid}`), - axios.get(`https://pow.co/api/v1/content/${txid}/replies`), - axios.get(`https://onchain.sv/api/v1/events/${txid}`) + axios.get(`https://pow.co/api/v1/content/${txid}`).catch((err) => { + console.log(err) + return { data: {}} + }), + axios.get(`https://pow.co/api/v1/content/${txid}/replies`).catch((err) => { + console.log(err) + return { data: {}} + }), + axios.get(`https://onchain.sv/api/v1/events/${txid}`).catch((err) => { + console.log(err) + return { data: {}} + }) ]) - let { content } = contentResponse.data + let { content } = contentResponse.data || {} let { tags } = contentResponse.data let { events } = onchainData.data; - let difficulty = tags.reduce((acc: number, curr: any) => acc + curr.difficulty, 0) + let difficulty = tags?.reduce((acc: number, curr: any) => acc + curr.difficulty, 0) || 0 let replies = repliesResponse.data.replies || [] let inReplyToTx = contentResponse.data.context_txid || null let author, textContent, app, createdAt; let urls: string[] = [] - let files = [] + let files: BitcoinFile[] = [] if (twetchResult){ textContent = twetchResult.bContent @@ -109,23 +120,25 @@ async function getTransactionDetails(txid: string): Promise { + twetchResult.files && JSON.parse(twetchResult.files).map(async (fileTx:string) => { let src = `https://dogefiles.twetch.app/${fileTx}` let response = await fetch(src) let mime = response.headers.get("content-type") - return { - contentType: mime, - content: src + files.push({ + contentType: mime!, + content: src, + txid: fileTx - } + }) }) urls = parseURLsFromMarkdown(textContent) inReplyToTx = twetchResult.postByReplyPostId?.transaction - replies = twetchResult.postsByReplyPostId?.edges?.map((node:any) => node.transaction) + twetchResult.postsByReplyPostId?.edges?.map((node:any) => { + replies.push({txid: node.node.transaction}) + }) app = "twetch.com" createdAt = twetchResult.createdAt - } - if (content.bmap){ + } else if (content.bmap){ content.bmap.B.forEach((bContent: any) => { if (bContent['content-type'].includes("text")){ textContent = bContent.content @@ -171,8 +184,7 @@ async function getTransactionDetails(txid: string): Promise { if(evt.type === "url"){ urls.push(evt.content.url) @@ -190,6 +202,7 @@ async function getTransactionDetails(txid: string): Promise { + const details = await getTransactionDetails(txid) + return (
diff --git a/components/PostDetailCard.tsx b/components/PostDetailCard.tsx index 285faaa..9b1f7f2 100644 --- a/components/PostDetailCard.tsx +++ b/components/PostDetailCard.tsx @@ -1,5 +1,5 @@ 'use client' -import React from 'react' +import React, { useEffect } from 'react' import { TransactionDetails, URLPreview } from '../app/t/[txid]/page' import { useRouter } from 'next/navigation' import Link from 'next/link' @@ -14,7 +14,10 @@ interface PostDetailCardProps { const PostDetailCard = ({details}:PostDetailCardProps) => { const router = useRouter() const gradient = "from-pink-400 to-violet-600"; - console.log(details.tags) + + useEffect(() => { + console.log(details) + },[]) return (
{

-
+
{details.author?.paymail && ( { > {details.files!.map((media: any, index: number) => (
diff --git a/components/v13_BoostContentCardV2.tsx b/components/v13_BoostContentCardV2.tsx index 846ea9f..cf214a5 100644 --- a/components/v13_BoostContentCardV2.tsx +++ b/components/v13_BoostContentCardV2.tsx @@ -20,7 +20,7 @@ import Youtube from "react-youtube"; import { twetchDetailQuery } from "./Twetch"; import { NFTJig, relayDetailQuery } from "./RelayClub"; import ReactPlayer from "react-player/lazy"; -import LoveOrdButton from "./LoveOrdButton"; +import LoveOrdButton from "./v13_LoveOrdButton"; import { useRelay } from "../v13_context/RelayContext"; import { Meeting, getMeeting } from '../services/meetings' @@ -217,14 +217,27 @@ const BoostContentCardV2 = ({ useEffect(() => { getData().then((res) => { - parseContent(res.content); + console.log(content_txid,res) + if(res.twetchResult){ + setContentText(res.twetchResult.bContent || ""); + setPaymail(`${res.twetchResult.userId}@twetch.me`); + setUserName(res.twetchResult.userByUserId.name); + setPostMedia(JSON.parse(res.twetchResult.files) || []); + setInReplyTo(res.twetchResult.postByReplyPostId?.transaction || ""); + setTimestamp(moment(res.twetchResult.createdAt).unix()); + setCommentCount(res.twetchResult.postsByReplyPostId.totalCount); + setIsTwetch(true); + } else { + parseContent(res.content); + + } setTags(res.tags); if (!difficulty && res.tags) { setComputedDiff( res.tags.reduce((acc: number, curr: any) => acc + curr.difficulty, 0) ); } - setCommentCount(res.replies.length); + setCommentCount(commentCount + res.replies.length); setLoading(false); }); }, []); @@ -286,20 +299,7 @@ const BoostContentCardV2 = ({ if (content.bmap) { if (content.bmap.B && content.bmap.MAP) { - if (content.bmap.MAP[0].app === "twetch") { - console.log("this is a twetch"); - twetchDetailQuery(content_txid).then((res) => { - console.log(res); - setContentText(res.bContent || ""); - setPaymail(`${res.userId}@twetch.me`); - setUserName(res.userByUserId.name); - setPostMedia(JSON.parse(res.files) || []); - setInReplyTo(res.postByReplyPostId?.transaction || ""); - setTimestamp(moment(res.createdAt).unix()); - setCommentCount(res.postsByReplyPostId.totalCount); - setIsTwetch(true); - }); - } else if ( + if ( content.bmap.MAP[0].app == "relayclub" && content.bmap.MAP[0].context === "club" ) { @@ -417,7 +417,8 @@ const BoostContentCardV2 = ({ }; const getData = async () => { - const [contentResult, repliesResult] = await Promise.all([ + const [twetchResult ,contentResult, repliesResult] = await Promise.all([ + twetchDetailQuery(content_txid).catch((err) => console.log(err)), axios.get(`${BASE}/content/${content_txid}`).catch((err) => { console.error("api.content.fetch.error", err); return { data: { content: {} } }; @@ -432,7 +433,7 @@ const BoostContentCardV2 = ({ const { tags } = contentResult.data; const replies = repliesResult.data.replies || []; - return { content, tags, replies }; + return { twetchResult, content, tags, replies }; }; if (loading) { diff --git a/components/v13_LoveOrdButton.tsx b/components/v13_LoveOrdButton.tsx new file mode 100644 index 0000000..f4f9196 --- /dev/null +++ b/components/v13_LoveOrdButton.tsx @@ -0,0 +1,155 @@ +'use client' +import axios from 'axios'; +import React, { useEffect, useState } from 'react' +import { buildInscriptionASM } from '../services/inscriptions'; +import { useRelay } from '../v13_context/RelayContext'; +import toast from "react-hot-toast" +import Drawer from './v13_Drawer'; +import LoveOrdPopup from './v13_LoveOrdPopup'; +import WalletProviderPopUp from './v13_WalletProviderPopUp'; +import { useBitcoin } from '../v13_context/BitcoinContext'; + +interface LoveOrdButtonProps { + txid: string; + userPaymail: string; +} + +const LoveOrdButton = ({ txid, userPaymail }: LoveOrdButtonProps) => { + const { authenticated } = useBitcoin() + const [walletPopupOpen, setWalletPopupOpen] = useState(false) + const [base64String, setBase64String] = useState("") + const { relayOne, hasTwetchPrivilege } = useRelay() + const [loveOrdPopupOpen, setLoveOrdPopupOpen] = useState(false) + + useEffect(() => { + axios.get(`/api/v1/twetch/preview/${txid}`).then((resp) => { + setBase64String(resp.data) + }) + },[]) + + const handleLoveOrdClick = (e:any) => { + e.preventDefault() + e.stopPropagation() + if(!authenticated){ + setWalletPopupOpen(true) + return + } + setLoveOrdPopupOpen(true) + } + + const handleClose = (e:any) => { + e.preventDefault() + e.stopPropagation() + setLoveOrdPopupOpen(false) + } + + const inscribeLoveOrd = async (e:any) => { + e.preventDefault() + e.stopPropagation() + + toast('Inscribing your 1LoveOrd', { + icon: '⛏️', + style: { + borderRadius: '10px', + background: '#333', + color: '#fff', + }, + }); + let response = await axios.get(`/api/v1/twetch/preview/${txid}`) + setBase64String(response.data); + + // Warning: Transferring Or Liquidating Inscriptions Requires Access To Backup Seed Phrase + //@ts-ignore + const address = await relayOne.alpha.run.getOwner(); + + const inscriptionOutput = buildInscriptionASM({ + address, + dataB64: base64String, + contentType: 'image/png', + metaData: { + app: 'pow.co', + type: 'ord', + name: `1LoveOrd_${txid}`, + subtype: 'collectionItem', + subTypeData: JSON.stringify({ + collectionId: "" + }), + royalties: JSON.stringify([{ + type: 'paymail', + destination: userPaymail, + percentage: 0.0218 + }]), + previewURL: `https://cloud-functions.twetch.app/api/t/${txid}/unfurl`, + } + }) + + console.log({ inscriptionOutput }) + let outputs = [] + outputs.push({ + to: inscriptionOutput, + amount: 1e-6, // Inscribe the contents of your post on a 100 satoshi coin + currency: 'BSV', + }) + outputs.push({ + to: 'aristotelis@relayx.iio', + amount: 0.02, + currency: 'USD' + }) + + try { + let resp: any = await relayOne?.send({outputs}) + toast('Success!', { + icon: '✅', + style: { + borderRadius: '10px', + background: '#333', + color: '#fff', + }, + }); + console.log("1LoveOrd.submit.relayx.response", resp) + + } catch (error) { + + console.log(error) + toast('Error!', { + icon: '🐛', + style: { + borderRadius: '10px', + background: '#333', + color: '#fff', + }, + }); + + } + } + + if(!hasTwetchPrivilege){ + return
+ } + + return ( + <> +
+ + + +
+ setLoveOrdPopupOpen(false)} + > + setLoveOrdPopupOpen(false)}/> + + setWalletPopupOpen(false)} + > + setWalletPopupOpen(false)} /> + + + ) +} + +export default LoveOrdButton \ No newline at end of file diff --git a/components/v13_LoveOrdPopup.tsx b/components/v13_LoveOrdPopup.tsx new file mode 100644 index 0000000..37183c5 --- /dev/null +++ b/components/v13_LoveOrdPopup.tsx @@ -0,0 +1,173 @@ +'use client' +import React, { useMemo, useState } from 'react' +import { toast } from 'react-hot-toast'; +import { useRelay } from '../v13_context/RelayContext'; +import { buildInscriptionASM } from '../services/inscriptions'; +import { useRouter } from 'next/navigation'; + +interface LoveOrdPopupProps { + txid: string; + userPaymail: string; + twetchPreview: string; + onClose: () => void +} + +export const LoveOrdCollectionTxID = "1a9806fb54eb1cb6dea3bff3721eb90852d505159cb36d7227cc8673835ee62b" + +const bitcoinAddressRegex = /^(1|3)[A-HJ-NP-Za-km-z1-9]{25,34}$/; + +const LoveOrdPopup = ({ txid, userPaymail, twetchPreview, onClose }: LoveOrdPopupProps) => { + const [ordinalsAddress, setOrdinalsAddress] = useState("") + const { relayOne } = useRelay() + const router = useRouter() + + const handleChangeOrdinalsAddress = (e:any) => { + e.preventDefault() + setOrdinalsAddress(e.target.value) + } + + const handleClose = (e:any) => { + e.preventDefault() + e.stopPropagation() + onClose() + } + + const handleMintLoveOrd = async (e:any) => { + e.preventDefault() + + toast('Inscribing your 1LoveOrd', { + icon: '⛏️', + style: { + borderRadius: '10px', + background: '#333', + color: '#fff', + }, + }); + let address + if(ordinalsAddress && bitcoinAddressRegex.test(ordinalsAddress)){ + address = ordinalsAddress + } else { + address = await relayOne?.alpha.run.getOwner() + } + + const inscriptionOutput = buildInscriptionASM({ + address, + dataB64: twetchPreview, + contentType: 'image/png', + metaData: { + app: 'pow.co', + type: 'ord', + name: `1LoveOrd_${txid}`, + subtype: 'collectionItem', + subTypeData: JSON.stringify({ + collectionId: LoveOrdCollectionTxID + }), + royalties: JSON.stringify([{ + type: 'paymail', + destination: userPaymail, + percentage: 0.0218 + }]), + previewURL: `https://cloud-functions.twetch.app/api/t/${txid}/unfurl`, + } + }) + + console.log({ inscriptionOutput }) + + console.log(userPaymail) + let outputs = [] + outputs.push({ + to: inscriptionOutput, + amount: 1e-8, + currency: 'BSV', + }) + outputs.push({ + to: '1Hd2gJyBxQZvVuCtcX26DFRDGNcWA5L9Eo', + amount: 0.02, + currency: 'USD' + }) + /* + outputs.push({ + to: userPaymail, + amount: 0.03, + currency: "USD" + }) */ + + try { + let resp: any = await relayOne?.send({outputs}) + console.log("1LoveOrd.submit.relayx.response", resp) + router.replace(`/${resp.txid}`) + onClose() + toast('Success!', { + icon: '✅', + style: { + borderRadius: '10px', + background: '#333', + color: '#fff', + }, + }); + + } catch (error) { + + console.log(error) + toast('Error!', { + icon: '🐛', + style: { + borderRadius: '10px', + background: '#333', + color: '#fff', + }, + }); + + } + } + return ( +
+
+
+
e.stopPropagation()} className='flex'> +
+
+
+

+ ❤️ 1LoveOrd ❤️ +

+

+ Bring some Twetch love back to your wallet +

+
+
+ Twetch Preview +
+ +

* If you leave this blank, the inscription will be minted to your RelayX Run address. (RelayX Ordinals Wallet interface 🔜)

+ +
+
+
+
+
+
+
+
+ ) +} + +export default LoveOrdPopup \ No newline at end of file diff --git a/components/v13_WalletProviderPopUp.tsx b/components/v13_WalletProviderPopUp.tsx new file mode 100644 index 0000000..0e1e060 --- /dev/null +++ b/components/v13_WalletProviderPopUp.tsx @@ -0,0 +1,462 @@ +'use client' +import React, { useContext, useState } from "react"; +import { useRelay } from "../v13_context/RelayContext"; +import { useBitcoin } from "../v13_context/BitcoinContext"; +import { useTwetch } from "../v13_context/TwetchContext"; +import { useHandCash } from "../v13_context/HandCashContext"; +import { useLocalWallet } from "../v13_context/LocalWalletContext"; +import { useSensilet } from "../v13_context/SensiletContext"; +import useWallet from "../hooks/v13_useWallet"; + +interface WalletProviderProps { + onClose: () => void +} + +const WalletProviderPopUp = ({ onClose }: WalletProviderProps) => { + const { setWallet, authenticate } = useBitcoin() + const [seedInputScreen, setSeedInputScreen] = useState(false) + const { relayxAuthenticate, relayxAuthenticated, relayxPaymail, relayxWallet, relayxLogout } = useRelay() + const { twetchAuthenticate, twetchAuthenticated, twetchPaymail, twetchWallet, twetchLogout } = useTwetch() + const { handcashAuthenticate, handcashAuthenticated, handcashPaymail, handcashLogout } = useHandCash() + const { sensiletAuthenticate, sensiletAuthenticated, sensiletWallet, sensiletPaymail, sensiletLogout } = useSensilet() + + const { localWalletAuthenticate, localWalletAuthenticated, localWalletPaymail, localWallet, seedPhrase, localWalletLogout } = useLocalWallet() + const [inputSeedPhrase, setInputSeedPhrase] = useState(seedPhrase) + + const wallet = useWallet() + + async function handleSelectWallet(name: string) { + + switch(name) { + + case 'local': + + if (localWallet) { + + setWallet('local') + + onClose() + + } else { + + setSeedInputScreen(true) + + } + + break; + + case 'relayx': + + if (!relayxWallet) { + + await relayxAuthenticate() + + } + + setWallet('relayx') + + onClose() + + break; + + + case 'sensilet': + + if (!sensiletWallet) { + + await handleSensiletAuth() + + } else { + + setWallet('sensilet') + + onClose() + + } + + break; + + + case 'twetch': + + if (!twetchWallet) { + + handleTwetchAuth() + + } + + setWallet('twetch') + + onClose() + + break; + + } + + } + + const handleRelayxAuth = async (e:any) => { + e.preventDefault() + try { + if (!relayxAuthenticated){ + await relayxAuthenticate() + } + setWallet("relayx") + onClose() + } catch (error) { + console.log(error) + } + + } + + const handleTwetchAuth = async () => { + try { + if (!twetchAuthenticated){ + await twetchAuthenticate() + } + setWallet("twetch") + onClose() + } catch (error) { + console.log(error) + } + } + + const handleHandcashAuth = async (e:any) => { + e.preventDefault() + try { + if (!handcashPaymail) { + await handcashAuthenticate() + } + setWallet("handcash") + + onClose() + } catch (error) { + console.log(error) + } + } + + const handleSensiletAuth = async () => { + try { + await sensiletAuthenticate() + setWallet("sensilet") + onClose() + } catch (error) { + console.log(error) + } + } + + const handleSeedAuth = async (e:any) => { + e.preventDefault() + try { + if (inputSeedPhrase.length > 0){ //TODO Additional Checks might be necessary + await localWalletAuthenticate(inputSeedPhrase) + } else { + throw new Error("Invalid Seed Phrase") + } + setWallet("local") + onClose() + } catch (error) { + console.log(error) + } + } + + const handleChangeSeedPhrase = (e:any) => { + e.preventDefault() + setInputSeedPhrase(e.target.value) + } + + const handleRelayxLogout = (e:any) => { + e.preventDefault() + e.stopPropagation() + relayxLogout() + if (twetchAuthenticated){ + setWallet("twetch") + } else if (handcashAuthenticated) { + setWallet("handcash") + } else if (sensiletAuthenticated) { + setWallet("sensilet") + } else if (localWalletAuthenticated) { + setWallet("local") + } else { + setWallet(null) + } + } + + const handleTwetchLogout = (e:any) => { + e.preventDefault() + e.stopPropagation() + twetchLogout() + if (relayxAuthenticated){ + setWallet("relayx") + } else if (handcashAuthenticated) { + setWallet("handcash") + } else if (sensiletAuthenticated) { + setWallet("sensilet") + } else if (localWalletAuthenticated) { + setWallet("local") + } else { + setWallet(null) + } + } + + const handleHandcashLogout = (e:any) => { + e.preventDefault() + e.stopPropagation() + handcashLogout() + if (twetchAuthenticated){ + setWallet("twetch") + } else if (relayxAuthenticated) { + setWallet("relayx") + } else if (sensiletAuthenticated) { + setWallet("sensilet") + } else if (localWalletAuthenticated) { + setWallet("local") + } else { + setWallet(null) + } + } + + const handleSensiletLogout = (e:any) => { + e.preventDefault() + e.stopPropagation() + sensiletLogout() + if (twetchAuthenticated){ + setWallet("twetch") + } else if (handcashAuthenticated) { + setWallet("handcash") + } else if (relayxAuthenticated) { + setWallet("relayx") + } else if (localWalletAuthenticated) { + setWallet("local") + } else { + setWallet(null) + } + } + + const handleLocalWalletLogout = (e:any) => { + e.preventDefault() + e.stopPropagation() + localWalletLogout() + if (twetchAuthenticated){ + setWallet("twetch") + } else if (handcashAuthenticated) { + setWallet("handcash") + } else if (sensiletAuthenticated) { + setWallet("sensilet") + } else if (relayxAuthenticated) { + setWallet("relayx") + } else { + setWallet(null) + } + } + + return ( +
+
+
+
+
+ {!seedInputScreen ? (
+

+ Select Wallet +

+ + )} + + + + )} + + + + )} + + + + )} + + + )} + +
):( +
+ +
+

+ Input Seed +

+