Skip to content
Draft
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: 2 additions & 0 deletions apps/demo-dapp-with-react-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
},
"dependencies": {
"@reown/appkit-universal-connector": "^1.8.14",
"@ton-api/client": "^0.4.0",
"@ton-api/ton-adapter": "^0.4.1",
"@ton-community/assets-sdk": "0.0.5",
"@ton/core": "0.61.0",
"@ton/crypto": "3.3.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
import { useEffect, useState } from 'react';
import { useTonConnectUI, useTonWallet, CHAIN } from '@tonconnect/ui-react';
import { TonClient, JettonWallet } from '@ton/ton';
import { Address, beginCell, fromNano, toNano } from '@ton/core';
import {
TonClient,
JettonWallet,
internal,
WalletContractV5R1,
loadMessageRelaxed
} from '@ton/ton';
import {
Address,
beginCell,
fromNano,
storeMessage,
storeMessageRelaxed,
toNano,
external,
Cell
} from '@ton/core';
import { JettonMinter, storeJettonTransferMessage } from '@ton-community/assets-sdk';
import './style.scss';
import { retry } from '../../server/utils/transactions-utils';
import { formatUnits, parseUnits } from '../../utils/units';
import { TonApiClient } from '@ton-api/client';
import { ContractAdapter } from '@ton-api/ton-adapter';

const USDT_MASTER = Address.parse('EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs');
const USDT_DECIMALS = 6;
Expand Down Expand Up @@ -127,6 +144,151 @@
});
};

const handleSendGasless = async () => {
const ta = new TonApiClient({
baseUrl: 'https://tonapi.io'
});
const provider = new ContractAdapter(ta);

const OP_CODES = {
TK_RELAYER_FEE: 0x878da6e3,
JETTON_TRANSFER: 0xf8a7ea5
};

const BASE_JETTON_SEND_AMOUNT = toNano(0.05);
if (!wallet) {
tonConnectUi.openModal();
return;
}

if (!destination || !senderAddress || !jettonWallet) {
return;
}

const amountUSDT = parseUnits(amount, USDT_DECIMALS);

if (!(amountUSDT > 0)) {
return;
}

const workchain = 0;
const walletV5 = WalletContractV5R1.create({
workchain,
publicKey: Buffer.from(wallet.account.publicKey!, 'hex')
});
const contract = provider.open(walletV5);

const jettonWalletAddressResult = await ta.blockchain.execGetMethodForBlockchainAccount(
USDT_MASTER,
'get_wallet_address',
{
args: [walletV5.address.toRawString()]
}
);
console.log('jettonWalletAddressResult', jettonWalletAddressResult);

Check warning on line 188 in apps/demo-dapp-with-react-ui/src/components/TransferUsdt/TransferUsdt.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement. Only these console methods are allowed: debug, error, info, warn
const jettonWalletUsdt = Address.parse(
jettonWalletAddressResult.decoded.jetton_wallet_address
);

// we use USDt in this example,
// so we just print all supported gas jettons and get the relay address.
// we have to send excess to the relay address in order to make a transfer cheaper.
const relayerAddress = await printConfigAndReturnRelayAddress();

// Create payload for jetton transfer
const tetherTransferPayload = beginCell()
.storeUint(OP_CODES.JETTON_TRANSFER, 32)
.storeUint(0, 64)
.storeCoins(amountUSDT) // 1 USDT
.storeAddress(Address.parse(destination)) // address for receiver
.storeAddress(relayerAddress) // address for excesses
.storeBit(false)
.storeCoins(1n)
.storeMaybeRef(undefined)
.endCell();

const messageToEstimate = beginCell()
.storeWritable(
storeMessageRelaxed(
internal({
to: jettonWalletUsdt,
bounce: true,
value: BASE_JETTON_SEND_AMOUNT,
body: tetherTransferPayload
})
)
)
.endCell();

const params = await ta.gasless.gaslessEstimate(USDT_MASTER, {
walletAddress: walletV5.address,
walletPublicKey: walletV5.publicKey.toString('hex'),
messages: [{ boc: messageToEstimate }]
});

console.log('Estimated transfer:', params);

Check warning on line 229 in apps/demo-dapp-with-react-ui/src/components/TransferUsdt/TransferUsdt.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement. Only these console methods are allowed: debug, error, info, warn

const { boc: internalBoc } = await tonConnectUi.signMessage({
validUntil: Math.ceil(Date.now() / 1000) + 5 * 60,
messages: params.messages.map(message => ({
address: message.address.toString(),
amount: message.amount,
stateInit: message.stateInit?.toBoc()?.toString('base64'),
payload: message.payload?.toBoc()?.toString('base64')
}))
});

// const tetherTransferForSend = walletV5.createTransfer({
// seqno,
// authType: 'internal',
// timeout: Math.ceil(Date.now() / 1000) + 5 * 60,
// secretKey: keyPair.secretKey,
// sendMode: SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS,
// messages: params.messages.map(message =>
// internal({
// to: message.address,
// value: BigInt(message.amount),
// body: message.payload
// })
// )
// });

const internalMsg = Cell.fromBase64(internalBoc);

const msg = loadMessageRelaxed(internalMsg.asSlice());

const seqno = await contract.getSeqno();
const extMessage = beginCell()
.storeWritable(
storeMessage(
external({
to: contract.address,
init: seqno === 0 ? contract.init : undefined,
body: msg.body
})
)
)
.endCell();

ta.gasless
.gaslessSend({
walletPublicKey: walletV5.publicKey.toString('hex'),
boc: extMessage
})
.then(() => console.log('A gasless transfer sent!'))

Check warning on line 278 in apps/demo-dapp-with-react-ui/src/components/TransferUsdt/TransferUsdt.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement. Only these console methods are allowed: debug, error, info, warn
.catch(error => console.error(error.message));

async function printConfigAndReturnRelayAddress(): Promise<Address> {
const cfg = await ta.gasless.gaslessConfig();

console.log('Available jettons for gasless transfer');

Check warning on line 284 in apps/demo-dapp-with-react-ui/src/components/TransferUsdt/TransferUsdt.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement. Only these console methods are allowed: debug, error, info, warn
console.log(cfg.gasJettons.map(gasJetton => gasJetton.masterId));

Check warning on line 285 in apps/demo-dapp-with-react-ui/src/components/TransferUsdt/TransferUsdt.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement. Only these console methods are allowed: debug, error, info, warn

console.log(`Relay address to send fees to: ${cfg.relayAddress}`);

Check warning on line 287 in apps/demo-dapp-with-react-ui/src/components/TransferUsdt/TransferUsdt.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement. Only these console methods are allowed: debug, error, info, warn
return cfg.relayAddress;
}
};

const loader = (
<span
className="loader"
Expand Down Expand Up @@ -169,9 +331,14 @@
</div>

{wallet ? (
<button onClick={handleSend} disabled={loading}>
{loading ? 'Loading wallet info...' : 'Send USDT'}
</button>
<>
<button onClick={handleSend} disabled={loading}>
{loading ? 'Loading wallet info...' : 'Send USDT'}
</button>
<button onClick={handleSendGasless} disabled={loading}>
{loading ? 'Loading wallet info...' : 'Send Gasless'}
</button>
</>
) : (
<button onClick={() => tonConnectUi.openModal()}>
Connect wallet to send USDT
Expand Down
42 changes: 39 additions & 3 deletions apps/demo-dapp-with-react-ui/src/components/TxForm/TxForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { beginCell } from '@ton/ton';
import ReactJson, { InteractionProps } from 'react-json-view';
import './style.scss';
import { SendTransactionRequest, useTonConnectUI, useTonWallet } from '@tonconnect/ui-react';
import { SignMessageResponse } from '@tonconnect/sdk';
import { TonProofDemoApi } from '../../TonProofDemoApi';
import { CHAIN } from '@tonconnect/ui-react';

Expand Down Expand Up @@ -33,6 +34,8 @@ export function TxForm() {
const [txResult, setTxResult] = useState<object | null>(null);
const [loading, setLoading] = useState(false);
const [waitingTx, setWaitingTx] = useState(false);
const [signLoading, setSignLoading] = useState(false);
const [signResult, setSignResult] = useState<SignMessageResponse | null>(null);

const wallet = useTonWallet();
const [tonConnectUi] = useTonConnectUI();
Expand Down Expand Up @@ -60,6 +63,22 @@ export function TxForm() {
}
};

const handleSignMessage = async () => {
setSignResult(null);
setSignLoading(true);
try {
const signedMessage = await tonConnectUi.signMessage(tx);
setSignResult(signedMessage);
} catch (error) {
console.error('Error signing message:', error);
setSignResult({
error: error instanceof Error ? error.message : 'Unknown error'
} as unknown as SignMessageResponse);
} finally {
setSignLoading(false);
}
};

return (
<div className="send-tx-form">
<h3>Configure and send transaction</h3>
Expand Down Expand Up @@ -112,9 +131,17 @@ export function TxForm() {
)}

{wallet ? (
<button onClick={handleSendTx} disabled={loading || waitingTx}>
{loading ? 'Sending...' : 'Send transaction'}
</button>
<div style={{ display: 'flex', gap: '12px', marginTop: '12px' }}>
<button onClick={handleSendTx} disabled={loading || waitingTx}>
{loading ? 'Sending...' : 'Send transaction'}
</button>
<button
onClick={handleSignMessage}
disabled={signLoading || loading || waitingTx}
>
{signLoading ? 'Signing...' : 'Sign transaction'}
</button>
</div>
) : (
<button onClick={() => tonConnectUi.openModal()}>
Connect wallet to send the transaction
Expand All @@ -130,6 +157,15 @@ export function TxForm() {
</>
)}

{signResult && (
<>
<div className="find-transaction-demo__json-label">Signed Message</div>
<div className="find-transaction-demo__json-view">
<ReactJson src={signResult} name={false} theme="ocean" collapsed={false} />
</div>
</>
)}

<style>{`
@keyframes spin {
0% { transform: rotate(0deg); }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { SendTransactionRpcRequest } from './send-transaction-rpc-request';
import { SignDataRpcRequest } from './sign-data-rpc-request';
import { SignMessageRpcRequest } from './sign-message-rpc-request';
import { RpcMethod } from '../../rpc-method';
import { DisconnectRpcRequest } from './disconnect-rpc-request';

export type RpcRequests = {
sendTransaction: SendTransactionRpcRequest;
signData: SignDataRpcRequest;
signMessage: SignMessageRpcRequest;
disconnect: DisconnectRpcRequest;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { AppRequest, RpcRequests } from './app-request';
export { SendTransactionRpcRequest } from './send-transaction-rpc-request';
export { SignDataRpcRequest } from './sign-data-rpc-request';
export { SignMessageRpcRequest } from './sign-message-rpc-request';
export { DisconnectRpcRequest } from './disconnect-rpc-request';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface SignMessageRpcRequest {
method: 'signMessage';
params: [string];
id: string;
}
2 changes: 1 addition & 1 deletion packages/protocol/src/models/rpc-method.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export type RpcMethod = 'disconnect' | 'sendTransaction' | 'signData';
export type RpcMethod = 'disconnect' | 'sendTransaction' | 'signData' | 'signMessage';
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export {
SignDataRpcResponseError,
SIGN_DATA_ERROR_CODES
} from './sign-data-rpc-response';
export {
SignMessageRpcResponse,
SignMessageRpcResponseSuccess,
SignMessageRpcResponseError,
SIGN_MESSAGE_ERROR_CODES
} from './sign-message-rpc-response';
export {
DisconnectRpcResponse,
DisconnectRpcResponseSuccess,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {
WalletResponseTemplateError,
WalletResponseTemplateSuccess
} from './wallet-response-template';

export type SignMessageRpcResponse = SignMessageRpcResponseSuccess | SignMessageRpcResponseError;

export interface SignMessageRpcResponseSuccess extends WalletResponseTemplateSuccess {}

export interface SignMessageRpcResponseError extends WalletResponseTemplateError {
error: { code: SIGN_MESSAGE_ERROR_CODES; message: string; data?: unknown };
id: string;
}

export enum SIGN_MESSAGE_ERROR_CODES {
UNKNOWN_ERROR = 0,
BAD_REQUEST_ERROR = 1,
UNKNOWN_APP_ERROR = 100,
USER_REJECTS_ERROR = 300,
METHOD_NOT_SUPPORTED = 400
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import {
SendTransactionRpcResponseSuccess
} from './send-transaction-rpc-response';
import { SignDataRpcResponseError, SignDataRpcResponseSuccess } from './sign-data-rpc-response';
import {
SignMessageRpcResponseError,
SignMessageRpcResponseSuccess
} from './sign-message-rpc-response';
import {
DisconnectRpcResponseError,
DisconnectRpcResponseSuccess
Expand All @@ -20,6 +24,11 @@ export type RpcResponses = {
success: SignDataRpcResponseSuccess;
};

signMessage: {
error: SignMessageRpcResponseError;
success: SignMessageRpcResponseSuccess;
};

disconnect: {
error: DisconnectRpcResponseError;
success: DisconnectRpcResponseSuccess;
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/models/methods/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './connect';
export * from './send-transaction';
export * from './sign-data';
export * from './sign-message';
2 changes: 2 additions & 0 deletions packages/sdk/src/models/methods/sign-message/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { SignMessageRequest } from './sign-message-request';
export { SignMessageResponse } from './sign-message-response';
Loading