Skip to content

Commit

Permalink
Telegram miniapp sepolia wallet integration (#359)
Browse files Browse the repository at this point in the history
* fix: wallet connect fix

* fix: anotate types

* feat/fix: Interacting with telegram argentbot to make tx also fixed walletconnect on pwa

* env

* fix: https from pkk json
  • Loading branch information
addegbenga authored Dec 14, 2024
1 parent a1eafac commit 7d8fad6
Show file tree
Hide file tree
Showing 9 changed files with 555 additions and 305 deletions.
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

0 comments on commit 7d8fad6

Please sign in to comment.