Skip to content

Commit

Permalink
feat: sign tx
Browse files Browse the repository at this point in the history
  • Loading branch information
duanyytop committed Dec 1, 2023
1 parent b9d5f6b commit ff797ce
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 30 deletions.
84 changes: 58 additions & 26 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {useQuery} from "react-query";
import {useWebApp} from "@vkruglikov/react-telegram-web-app"
import {WebApp} from "@vkruglikov/react-telegram-web-app/lib/core/twa-types";
import "./App.css";
import { buildConnectTokenAndUrl, buildSendTxTokenAndUrl, buildSignMsgTokenAndUrl } from "./helper";
import { buildConnectTokenAndUrl, buildSendTxTokenAndUrl, buildSignMsgTokenAndUrl, buildSignTxTokenAndUrl } from "./helper";
import {api, ConnectResp, QueryKey, SendTxResp, SignResp, SignTxResp} from "./api";

const USER_REJECTED = 'rejected'
Expand All @@ -18,12 +18,13 @@ export default function App() {

const [connectLoading, setConnectLoading] = useState(false);
const [signLoading, setSignLoading] = useState(false);
const [signTxLoading, setSignTxLoading] = useState(false);
const [sendLoading, setSendLoading] = useState(false);

const [connectToken, setConnectToken] = useState("");
const [signToken, setSignToken] = useState("");
const [signTxToken, setSignTxToken] = useState("")
const [sendToken, setSendToken] = useState("");
const [isSend, setIsSend] = useState(false)

const openUrl = (url: string) => {
webApp.openLink && webApp.openLink(url);
Expand Down Expand Up @@ -84,24 +85,32 @@ export default function App() {
useQuery(
[QueryKey.GetBotMessage, "signTx"],
async () => {
const {signature} = await api.getTgBotMessage<SignTxResp>(sendToken);
const {signature} = await api.getTgBotMessage<SignTxResp>(signTxToken);
return signature;
},
{
enabled: !!webApp.initData && sendLoading && !!address && !isSend,
enabled: !!webApp.initData && signTxLoading && !!address,
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
refetchInterval: 500,
retry: 240,
onSuccess(sig) {
setSendLoading(false);
async onSuccess(sig) {
setSignTxLoading(false);
if (sig === USER_REJECTED) {
showAlert("User refuses to sign the transaction");
} else {
showAlert(`Transaction signed successfully with the signature: ${sig}`);
// You can call the RPC eth_sendRawTransaction with the signature
// const transport = http(SEPOLIA_RPC);
// const client = createPublicClient({
// chain: sepolia,
// transport,
// });
// const hash = await client.sendRawTransaction({
// serializedTransaction: signature as Hex,
// });
alert(`Transaction signed successfully with the signature: ${sig}`);
}

},
}
);
Expand All @@ -113,7 +122,7 @@ export default function App() {
return txHash;
},
{
enabled: !!webApp.initData && sendLoading && !!address && isSend,
enabled: !!webApp.initData && sendLoading && !!address,
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
Expand All @@ -129,7 +138,6 @@ export default function App() {
},
}
);


const onConnect = () => {
if (webApp.initData.length === 0) {
Expand Down Expand Up @@ -161,7 +169,7 @@ export default function App() {
}
}

const onSendTx = async (isSend: boolean) => {
const onSignTx = async () => {
if (webApp.initData.length === 0) {
alert("Please open the web app in Telegram");
return;
Expand All @@ -172,17 +180,36 @@ export default function App() {
from: address! as Hex,
value: parseEther(amount.toString()).toString(),
};
const {token, url} = buildSendTxTokenAndUrl(webApp.initData, address!, tx, isSend);
setSendToken(token);
setSendLoading(true);
setIsSend(isSend)
const {token, url} = buildSignTxTokenAndUrl(webApp.initData, address!, tx);
setSignTxToken(token);
setSignTxLoading(true);
openUrl(url);
} catch (error) {
console.log(error);
}
};


const onSendTx = async () => {
if (webApp.initData.length === 0) {
alert("Please open the web app in Telegram");
return;
}
try {
const tx = {
to: toAddress as Hex,
from: address! as Hex,
value: parseEther(amount.toString()).toString(),
};
const {token, url} = buildSendTxTokenAndUrl(webApp.initData, address!, tx);
setSendToken(token);
setSendLoading(true);
openUrl(url);
} catch (error) {
console.log(error);
}
};

return (
<div id="app">
<div className="text-2xl sticky font-bold text-center">Mini App wallet connect demo</div>
Expand All @@ -199,15 +226,15 @@ export default function App() {
onChange={(e) => setMessage(e.currentTarget.value)}
/>
<div>
<button className="btn btn-primary mt-[10px] w-[120px] capitalize" disabled={!message} onClick={onSignMessage}>
<button className="btn btn-primary mt-[10px] w-[120px] capitalize" disabled={!message || signLoading} onClick={onSignMessage}>
{signLoading ? <span className="loading loading-spinner loading-md" /> : "Sign"}
</button>
</div>
<div className="divider" />
</div>

<div className="my-[30px]">
<h2 className="text-xl">Send transaction: </h2>
<h2 className="text-xl">Sign and Send transaction: </h2>
<input
type="text"
placeholder={toAddress ? toAddress : "To address"}
Expand All @@ -220,11 +247,19 @@ export default function App() {
className="input input-bordered input-accent w-full max-w-xs mt-[8px]"
onChange={(e) => setAmount(Number(e.currentTarget.value))}
/>
<div>
<button className="btn btn-primary mt-[10px] w-[80px] capitalize" disabled={amount <= 0 || !toAddress} onClick={() => onSendTx(false)}>
{sendLoading ? <span className="loading loading-spinner loading-md" /> : "Sign"}
<div className="mt-[10px]">
<button
className="btn btn-primary w-[120px] capitalize block"
disabled={amount <= 0 || !toAddress || signTxLoading}
onClick={onSignTx}
>
{signTxLoading ? <span className="loading loading-spinner loading-md" /> : "Sign"}
</button>
<button className="btn btn-primary mt-[10px] w-[80px] capitalize" disabled={amount <= 0 || !toAddress} onClick={() => onSendTx(true)}>
<button
className="btn btn-primary w-[120px] capitalize mt-[12px] block"
disabled={amount <= 0 || !toAddress || sendLoading}
onClick={onSendTx}
>
{sendLoading ? <span className="loading loading-spinner loading-md" /> : "Send"}
</button>
</div>
Expand All @@ -233,18 +268,15 @@ export default function App() {

<button
className="btn btn-primary capitalize w-[120px]"
onClick={() => {
setAddress(null);
}}
onClick={() => setAddress(null)}
>
Disconnect
</button>

<div className="divider" />
</div>
) : (
<div className="text-center">
<button className="btn btn-primary capitalize w-[200px] mt-[30px]" onClick={() => onConnect()}>
<button className="btn btn-primary capitalize w-[200px] mt-[30px]" disabled={connectLoading} onClick={onConnect}>
{connectLoading ? <span className="loading loading-spinner loading-md" /> : "JoyID Passkey connect"}
</button>
</div>
Expand Down
42 changes: 38 additions & 4 deletions src/helper/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { Hex, keccak256 } from "viem"
import { CALLBACK_SERVER_URL, JOYID_APP_URL } from "../env";
import { TransactionRequest, buildConnectUrl, buildSignMessageUrl, buildSignTxURL } from "@joyid/miniapp";

export const SEPOLIA_RPC = "https://rpc.sepolia.org";
export const SEPOLIA_CHAIN_ID = 11155111;

export enum Action {
Connect,
SignMsg,
Expand Down Expand Up @@ -81,20 +84,51 @@ export const buildSignMsgTokenAndUrl = (initData: string, address: Hex, message:
* @param initData the initData of telegram web app object to generate token{@link https://core.telegram.org/bots/webapps#initializing-mini-apps}
* @param address the Ethereum address to sign message
* @param tx the Ethereum transaction to be signed and sended
* @param isSend If `false`, the Ethereum transaction will only be signed by JoyID Passkey Wallet
* and if `true`, the Ethereum transaction will be signed and sended by JoyID Passkey Wallet.
* @returns the token and url for transaction sending with JoyID Passkey Wallet
*/
export const buildSendTxTokenAndUrl = (initData: string, address: Hex, tx: TransactionRequest, isSend: boolean) => {
export const buildSignTxTokenAndUrl = (initData: string, address: Hex, tx: TransactionRequest) => {
const token = generateToken(initData, Action.SendTx);
const url = buildSignTxURL({
...BASE_INIT,
tx,
signerAddress: address,
miniAppToken: token,
callbackUrl: CALLBACK_SERVER_URL,
isSend,
isSend: false,
// the network and rpcURL can be empty and if empty, the chain will be the JoyID default chain
network: {
name: "Sepolia",
chainId: SEPOLIA_CHAIN_ID,
},
rpcURL: SEPOLIA_RPC,
});
return {token, url};
};


/**
* Build JoyID URL to send Ethereum transaction with JoyID Passkey Wallet
* and generate the unique token for connection between the mini app and server
* @param initData the initData of telegram web app object to generate token{@link https://core.telegram.org/bots/webapps#initializing-mini-apps}
* @param address the Ethereum address to sign message
* @param tx the Ethereum transaction to be signed and sended
* @returns the token and url for transaction sending with JoyID Passkey Wallet
*/
export const buildSendTxTokenAndUrl = (initData: string, address: Hex, tx: TransactionRequest) => {
const token = generateToken(initData, Action.SendTx);
const url = buildSignTxURL({
...BASE_INIT,
tx,
signerAddress: address,
miniAppToken: token,
callbackUrl: CALLBACK_SERVER_URL,
isSend: true,
// the network and rpcURL can be empty and if empty, the chain will be the JoyID default chain
// network: {
// name: "Sepolia",
// chainId: SEPOLIA_CHAIN_ID,
// },
// rpcURL: SEPOLIA_RPC,
});
return {token, url};
};

0 comments on commit ff797ce

Please sign in to comment.