Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Telegram miniapp sepolia wallet integration #359

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/pwa/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ACCOUNT_PRIVATE_KEY="0x"

PROVIDER_URL="https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_7/your_api_key"
NETWORK_NAME="SN_SEPOLIA" # SN_SEPOLIA, SN_MAIN
NEXT_PUBLIC_TELEGRAM_BOT_URL="https://t.me/afk_aligned_dev_bot"
NEXT_PUBLIC_TELEGRAM_BOT_URL=""
PINATA_API_KEY="YOUR_PINATA_API_KEY"
PINATA_SECRET_API_KEY="YOUR_PINATA_SECRET_API_KEY"
IPFS_GATEWAY="https://ipfs.io/"
Expand Down
3 changes: 2 additions & 1 deletion apps/pwa/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@starknet-wc/core": "0.0.4",
"@starknet-wc/react": "0.0.4",
"@tanstack/react-query": "^5.40.0",
"@twa-dev/sdk": "^8.0.1",
"axios": "^1.7.2",
"common": "workspace:*",
"d3": "^7.9.0",
Expand Down Expand Up @@ -74,4 +75,4 @@
"typescript": "^5",
"web-vitals": "^2.1.4"
}
}
}
7 changes: 1 addition & 6 deletions apps/pwa/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import type {Metadata} from 'next';
// import {useRouter} from 'next/router';
import Script from 'next/script';

import {ArgentTMAProvider} from '@/context/argentTmContext';

import Providers from './providers';

export const metadata: Metadata = {
Expand Down Expand Up @@ -34,7 +32,6 @@ export default function RootLayout({children}: {children: React.ReactNode}) {
return (
<html lang="en">
<head>
<Script src="https://telegram.org/js/telegram-web-app.js" strategy="beforeInteractive" />
<Script
strategy="afterInteractive"
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
Expand All @@ -55,9 +52,7 @@ export default function RootLayout({children}: {children: React.ReactNode}) {
/>
</head>
<body>
<Providers>
<ArgentTMAProvider>{children}</ArgentTMAProvider>
</Providers>
<Providers>{children}</Providers>
</body>
</html>
);
Expand Down
258 changes: 212 additions & 46 deletions apps/pwa/src/components/telegram/index.tsx
Original file line number Diff line number Diff line change
@@ -1,85 +1,251 @@
'use client';
import {SessionAccountInterface} from '@argent/tma-wallet';
import {Button} from '@chakra-ui/react';
import {useEffect, useState} from 'react';
import {copyToClipboard} from '@/utils/copy';
import {ArgentTMA, SessionAccountInterface} from '@argent/tma-wallet';
import {Button, useToast} from '@chakra-ui/react';
import WebApp from '@twa-dev/sdk';
import {ART_PEACE_ADDRESS} from 'common';
import {useEffect, useMemo, useState} from 'react';
import {CallData, num, RPC} from 'starknet';

import {useArgentTMAContext} from '@/context/argentTmContext';
//Dummy Tx
export interface ExecuteContractActionOptions {
feeMultiplier?: number;
version?: number;
successMessage?: string;
errorMessage?: string;
}

export async function executeContractAction(
account: SessionAccountInterface,
myCall: any,
argentTMA: ArgentTMA,
options: ExecuteContractActionOptions = {},
) {
const {
feeMultiplier = 2, // Default to doubling estimated fees
version = 3,
} = options;

try {
// Estimate fees
const estimatedFee = await account.estimateInvokeFee([myCall], {version});

// Create resource bounds, multiplying l1_gas max amount
const resourceBounds = {
...estimatedFee.resourceBounds,
l1_gas: {
...estimatedFee.resourceBounds.l1_gas,
max_amount: num.toHex(
BigInt(parseInt(estimatedFee.resourceBounds.l1_gas.max_amount, 16) * feeMultiplier),
),
},
};

// Execute the transaction
const {transaction_hash} = await account.execute(myCall, {
version,
maxFee: estimatedFee.suggestedMaxFee,
feeDataAvailabilityMode: RPC.EDataAvailabilityMode.L1,
resourceBounds,
});

// Wait for transaction and get receipt
const receipt = await argentTMA.provider.waitForTransaction(transaction_hash);
console.log('Transaction receipt:', receipt);

return {
success: true,
transaction_hash,
receipt,
};
} catch (error) {
console.error(`Error performing`, error);

return {
success: false,
error,
};
}
}

// Separate configuration for Argent TMA initialization
const ARGENT_CONFIG = {
environment: 'sepolia',
appName: 'Hello world',
appTelegramUrl: process.env.NEXT_PUBLIC_TELEGRAM_BOT_URL || 'https://t.me/afk_aligned_dev_bot',
sessionParams: {
allowedMethods: [
{
contract:
process.env.NEXT_PUBLIC_STARKNET_CONTRACT_ADDRESS ||
'0x1c3e2cae24f0f167fb389a7e4c797002c4f0465db29ecf1753ed944c6ae746e',
selector: 'place_pixel',
},
// ... other methods
],
validityDays: 90,
},
};

// Utility function to initialize Argent TMA
const initArgentTMA = () => {
if (
typeof window !== 'undefined' &&
window.Telegram &&
WebApp.isActive &&
WebApp.platform !== 'unknown'
) {
return ArgentTMA.init(ARGENT_CONFIG as any);
}
return null;
};

export const TelegramAccount = () => {
const {argentTMA} = useArgentTMAContext();
const [accountTg, setAccount] = useState<SessionAccountInterface | undefined>();
const [isConnected, setIsConnected] = useState<boolean>(false);
const toast = useToast({
colorScheme: 'blackAlpha',
duration: 5000,
isClosable: true,
position: 'top-right',
});
const argentTMA = useMemo(() => initArgentTMA(), []);
const [txLoading, setTxLoading] = useState(false);
const [txHash, setTxHash] = useState('');

const [accountTg, setAccount] = useState<SessionAccountInterface | undefined>(() => {
// Initialize state from localStorage if available
if (typeof window !== 'undefined') {
const storedAddress = localStorage.getItem('telegramAccountAddress');
return storedAddress ? ({address: storedAddress} as SessionAccountInterface) : undefined;
}
return undefined;
});

const [isConnected, setIsConnected] = useState<boolean>(() => {
// Initialize connection state based on stored address
return !!localStorage.getItem('telegramAccountAddress');
});

useEffect(() => {
// Call connect() as soon as the app is loaded
argentTMA
?.connect()
.then((res) => {
async function connectArgent() {
try {
if (!argentTMA) return;

const res = await argentTMA.connect();
if (!res) {
// Not connected
setIsConnected(false);
localStorage.removeItem('telegramAccountAddress');
return;
}

if (accountTg?.getSessionStatus() !== 'VALID') {
// Session has expired or scope (allowed methods) has changed
// A new connection request should be triggered

// The account object is still available to get access to user's address
// but transactions can't be executed
const {account} = res;
const {account, callbackData} = res;

if (account.getSessionStatus() !== 'VALID') {
// Session has expired or scope (allowed methods) has changed
setAccount(account);
setIsConnected(false);
localStorage.removeItem('telegramAccountAddress');
return;
}

// Connected
const {account, callbackData} = res;
// The session account is returned and can be used to submit transactions
// Persist account address
localStorage.setItem('telegramAccountAddress', account.address);

setAccount(account);
setIsConnected(true);

// Custom data passed to the requestConnection() method is available here
console.log('callback data:', callbackData);
})
.catch((err) => {
console.error('Failed to connect', err);
});
} catch (error) {
console.error('Failed to connect:', error);
localStorage.removeItem('telegramAccountAddress');
}
}

connectArgent();
}, []);

const handleConnectButton = async () => {
// If not connected, trigger a connection request
// It will open the wallet and ask the user to approve the connection
// The wallet will redirect back to the app and the account will be available
// from the connect() method -- see above
try {
const resp = await argentTMA?.requestConnection('custom_callback_data');
console.log(resp, 'resp');
if (!argentTMA) return;
// Trigger a connection request
const resp = await argentTMA.requestConnection('custom_callback_data');
} catch (error) {
console.log(error, 'err');
}
};

// useful for debugging
// Useful for debugging
const handleClearSessionButton = async () => {
await argentTMA?.clearSession();
if (argentTMA) {
await argentTMA.clearSession();
}
// Clear localStorage
localStorage.removeItem('telegramAccountAddress');
setAccount(undefined);
setIsConnected(false);
};

//-->Handle Dummy Transaction
const handleTransaction = async () => {
setTxLoading(true);
try {
const timestamp = Math.floor(Date.now() / 1000);
const pixelCalldata = {
contractAddress: ART_PEACE_ADDRESS?.['0x534e5f5345504f4c4941'],
entrypoint: 'place_pixel',
calldata: CallData.compile({
position: '1',
color: '2',
now: timestamp,
}),
};
if (!accountTg && !argentTMA) return;
const resp = await executeContractAction(accountTg as any, pixelCalldata, argentTMA as any);

if (resp.success) {
setTxHash(`https://sepolia.starkscan.co/tx/${resp?.transaction_hash}`);
setTxLoading(false);
toast({
title: 'Transaction Successful',
status: 'success',
});
return;
}
setTxLoading(false);
toast({
title: 'Something went wrong',
status: 'error',
duration: 5000,
});
} catch (error) {
setTxLoading(false);
toast({
title: 'Something went wrong',
status: 'error',
});
}
};
const displayAddress = accountTg ? accountTg.address.slice(0, 8) : '';

return (
<>
<div>
{!isConnected && <Button onClick={handleConnectButton}>Connect</Button>}

{isConnected && (
<>
<p>
Account address: <code>{accountTg?.address}</code>
</p>
<div>
{!isConnected && <Button onClick={handleConnectButton}>Telegram Connect</Button>}

{isConnected && accountTg && (
<>
<p>
Account address: <code>{displayAddress}</code>
</p>
<div className="flex gap-2 pb-2">
<Button onClick={handleClearSessionButton}>Clear Session</Button>
</>
)}
</div>
</>
<Button disabled={txLoading} onClick={handleTransaction}>
{txLoading ? 'Transaction in progress' : 'Trigger transaction'}
</Button>
{txHash && <Button onClick={() => copyToClipboard(txHash)}>Copy Tx</Button>}
</div>
</>
)}
</div>
);
};
23 changes: 0 additions & 23 deletions apps/pwa/src/context/argentTmContext.tsx

This file was deleted.

Loading
Loading