Skip to content

Commit

Permalink
feat: twitter verify (#2824)
Browse files Browse the repository at this point in the history
* feat: twitter verify

* Fix code scanning alert no. 7: Incomplete URL substring sanitization

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

---------

Co-authored-by: jolestar <[email protected]>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Oct 28, 2024
1 parent f614bbf commit b16a5df
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 14 deletions.
2 changes: 2 additions & 0 deletions infra/rooch-portal-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@roochnetwork/rooch-sdk": "0.2.7",
"@roochnetwork/rooch-sdk-kit": "0.2.7",
"@tanstack/react-query": "^5.51.11",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-syntax-highlighter": "^15.5.13",
"autoprefixer": "^10.4.19",
"autosuggest-highlight": "^3.3.4",
Expand All @@ -57,6 +58,7 @@
"numeral": "^2.0.6",
"postcss": "^8.4.39",
"react": "^18.3.1",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.3.1",
"react-hook-form": "^7.51.5",
"react-syntax-highlighter": "^15.5.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default function SessionKeyGuardButton({ children }: { children: ReactNod
'0x1::*::*',
'0x3::*::*',
'0x176214bed3764a1c6a43dc1add387be5578ff8dbc263369f5bdc33a885a501ae::*::*',
'0x701c21bf1c8cd5af8c42983890d8ca55e7a820171b8e744c13f2d9998bf76cc3::*::*',
],
maxInactiveInterval: 60 * 60 * 8,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export function NavUpgrade({ sx, ...other }: StackProps) {
'0x1::*::*',
'0x3::*::*',
'0x176214bed3764a1c6a43dc1add387be5578ff8dbc263369f5bdc33a885a501ae::*::*',
'0x701c21bf1c8cd5af8c42983890d8ca55e7a820171b8e744c13f2d9998bf76cc3::*::*',
],
maxInactiveInterval: 60 * 60 * 8,
});
Expand Down
2 changes: 2 additions & 0 deletions infra/rooch-portal-v2/src/sections/settings/constant/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const TWITTER_ORACLE_PACKAGE_ID =
'0x701c21bf1c8cd5af8c42983890d8ca55e7a820171b8e744c13f2d9998bf76cc3';
168 changes: 165 additions & 3 deletions infra/rooch-portal-v2/src/sections/settings/view.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
'use client';

import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useCurrentAddress, useRoochClientQuery } from '@roochnetwork/rooch-sdk-kit';
import { useState, useEffect, useCallback } from 'react';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { Args, Transaction } from '@roochnetwork/rooch-sdk';
import {
useRoochClient,
useCurrentAddress,
useRoochClientQuery,
UseSignAndExecuteTransaction,
} from '@roochnetwork/rooch-sdk-kit';

import { Card, Chip, Stack, CardHeader, Typography, CardContent } from '@mui/material';
import { LoadingButton } from '@mui/lab';
import { Card, Chip, Stack, TextField, CardHeader, Typography, CardContent } from '@mui/material';

import { sleep } from 'src/utils/common';

import { DashboardContent } from 'src/layouts/dashboard';

import { toast } from 'src/components/snackbar';
import { Iconify } from 'src/components/iconify';

import { TWITTER_ORACLE_PACKAGE_ID } from './constant';
import SessionKeysTableCard from './components/session-keys-table-card';

export function SettingsView() {
const address = useCurrentAddress();
const router = useRouter();

const client = useRoochClient();

const [isAddressLoaded, setIsAddressLoaded] = useState(false);

const { mutateAsync: signAndExecuteTransaction } = UseSignAndExecuteTransaction();

const {
data: sessionKeys,
isPending: isLoadingSessionKeys,
Expand All @@ -40,6 +58,31 @@ export function SettingsView() {
}
}, [address, isAddressLoaded, router]);

const [twitterId, setTwitterId] = useState('');

const [verifying, setVerifying] = useState(false);

const getBindingTwitterId = useCallback(async () => {
if (!address) {
return;
}
const res = await client.executeViewFunction({
address: TWITTER_ORACLE_PACKAGE_ID,
module: 'twitter_account',
function: 'resolve_author_id_by_address',
args: [Args.address(address.toStr())],
});
setTwitterId((res.return_values?.[0].decoded_value as any).value.vec[0]);
// eslint-disable-next-line consistent-return
return (res.return_values?.[0].decoded_value as any)?.value.vec[0];
}, [address, client]);

useEffect(() => {
getBindingTwitterId();
}, [getBindingTwitterId]);

const [tweetId, setTweetId] = useState('');

return (
<DashboardContent maxWidth="xl">
<Card className="mt-4">
Expand All @@ -56,6 +99,125 @@ export function SettingsView() {
</Stack>
</CardContent>
</Card>
<Card className="mt-4">
<CardHeader
title={
<Stack direction="row" spacing={1.5} alignItems="center">
Twitter Binding <Iconify icon="logos:twitter" />
</Stack>
}
subheader="Bind a Twitter account to a Bitcoin address via publishing a tweet"
/>
<CardContent className="!pt-2">
{twitterId ? (
<Chip
className="justify-start w-fit"
color="success"
label={
<Stack direction="row" spacing={1.5} alignItems="center">
<Iconify icon="solar:check-circle-bold" />
Twitter Id: {twitterId}
</Stack>
}
/>
) : (
<Stack className="mt-2" spacing={1.5}>
<Stack spacing={1.5}>
<Stack className="font-medium" direction="row" spacing={0.5}>
1. Post the following text to you twitter account
<span className="font-normal text-sm text-gray-400">(Click it to copy)</span>
</Stack>
{address && (
<CopyToClipboard
text={`BTC:${address.toStr()} #RoochNetwork`}
onCopy={() => toast.success('Copy success')}
>
<Chip
variant="soft"
color="default"
className="w-fit font-semibold cursor-pointer"
label={`BTC:${address.toStr()} #RoochNetwork`}
/>
</CopyToClipboard>
)}
</Stack>
<Stack spacing={1.5}>
<Stack className="font-medium">
2. paste the link of the tweet into the input box below
</Stack>
<TextField
size="small"
className="w-full"
value={tweetId}
placeholder="https://x.com/RoochNetwork/status/180000000000000000"
onChange={(e) => {
setTweetId(e.target.value);
}}
/>
</Stack>
<LoadingButton
disabled={!tweetId || (() => {
try {
const url = new URL(tweetId);
return url.hostname !== 'x.com';
} catch {
return true;
}
})()}
color="inherit"
loading={verifying}
className="mt-2 w-fit"
variant="contained"
onClick={async () => {
try {
setVerifying(true);
const fetchTweetTxn = new Transaction();
const match = tweetId.match(/status\/(\d+)/);
if (match) {
const pureTweetId = match[1];
fetchTweetTxn.callFunction({
address: TWITTER_ORACLE_PACKAGE_ID,
module: 'tweet_fetcher',
function: 'fetch_tweet_entry',
args: [Args.string(pureTweetId)],
});
await signAndExecuteTransaction({ transaction: fetchTweetTxn });
await sleep(3000);
const verifyTxn = new Transaction();
verifyTxn.callFunction({
address: TWITTER_ORACLE_PACKAGE_ID,
module: 'twitter_account',
function: 'verify_and_binding_twitter_account',
args: [Args.string(pureTweetId)],
});
const res = await signAndExecuteTransaction({ transaction: verifyTxn });
if (res.execution_info.status.type !== 'executed') {
toast.error(
res.error_info?.vm_error_info.error_message ||
(res.output?.status.type === 'moveabort' &&
`Binding failed, abort code: ${res.output.status.abort_code}`) ||
'Binding failed'
);
}
await sleep(3000);
const checkRes = await getBindingTwitterId();
if (checkRes) {
toast.success('Binding success');
}
}
} catch (error) {
toast.error(String(error));
} finally {
setVerifying(false);
}
}}
>
Verify and bind Twitter account
</LoadingButton>
</Stack>
)}
</CardContent>
</Card>
<SessionKeysTableCard
sessionKeys={sessionKeys}
isPending={isLoadingSessionKeys}
Expand Down
45 changes: 34 additions & 11 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b16a5df

Please sign in to comment.