# @push: Non-Custodial Crypto Transfers on Telegram

Deep technical dive into @push architecture and cryptographic foundations. Learn how non-custodial chat-native transfers work with Telegram Ed25519 signatures, sender-paid gasless claiming, privacy-preserving chat binding, and smart contract security.

- URL: https://mytonwallet.io/en/blog/push-technical
- Markdown: https://mytonwallet.io/en/blog/push-technical.md
- Published: 2025-10-15T10:00:00+0000

---

### Introduction

**Transferring cryptocurrency** directly within a **chat environment** is an attractive concept. Decentralized finance remains in its early stages; integrating it into familiar everyday tools is a promising way to widen adoption.

One of the greatest benefits of such an approach is that transfers are **bound to chats** rather than blockchain addresses—as long as the sender and receiver share a chat, the sender **does not need to know** the receiver's address. The receiver **may not even have one** until they claim the transfer. In the meantime, the funds are securely held in an **escrow smart contract**, ensuring availability and protection without requiring the recipient to be blockchain-ready in advance.

Existing implementations on **Telegram** demonstrate that such integrations can be **intuitive and fast**, but so far they were built on **custodial** models—where the **service provider** holds and controls the assets until the recipient claims them. This may be convenient, but it deviates from the **original ideas** that gave rise to decentralized finance and blockchain technology in the first place.

The aim of **@push** is to reach the same seamless user experience while maintaining a **non-custodial** approach. No intermediary ever holds the private keys or has the ability to move funds. All assets are held in transparent smart contracts on the TON blockchain, and all transfers are verified using cryptographic signatures.

This article covers the **architecture** and **cryptographic foundations** of **@push**, along with technical solutions implemented to achieve both security and usability.

### What is @push?

**@push** is a Telegram bot enabling non-custodial, chat-native crypto transfers on Telegram. It supports both fungible tokens (TON, jettons) and NFTs (including Telegram Gifts), with bidirectional flows for both sending checks and requesting invoices. The sender signs and funds a check on-chain; the recipient proves identity with a Telegram Ed25519 signature and claims the transfer, without paying gas. All sensitive signing happens on the client; the backend only orchestrates messages and state.

[![A check message within a chat, Waiting for Signature and Ready to Receive Mini App interfaces](https://mytonwallet.io/img/articles/push-technical/figure1.png)](https://mytonwallet.io/img/articles/push-technical/figure1.png)

***[Figure 1](https://mytonwallet.io/img/articles/push-technical/figure1.png).** A check message within a chat, an activated inline mode; Mini App interfaces for tokens and NFTs shown from the sender’s and receiver’s perspectives.*

### Key Innovations

The **@push** system introduces several architectural and protocol-level mechanisms:

1. **Platform signature verification on-chain**. A novel approach to identity verification that leverages Telegram's Ed25519 signatures for authentication in smart contracts, eliminating the need for blockchain addresses prior to receiving funds.

2. **Sender-paid gasless claiming.** A mechanism enabling recipients to claim checks without possessing native cryptocurrency for transaction fees, achieved through prepaid gas storage within the smart contract.

3. **Privacy-preserving chat binding.** The use of anonymous chat instance identifiers for authorization without exposing actual chat IDs or membership information.

Together, they achieve a low-friction transfer experience consistent with the decentralized security model.

### System Architecture

The architecture of **@push** is designed to connect Telegram's user interface with secure on-chain asset transfers. It consists of four primary components:

1. **Telegram Bot.** Handles inline queries, generates check messages, and updates message states.

2. **Telegram Mini App.** Provides the interactive UI for signing and claiming checks, integrated with TON Connect.

3. **TON Smart Contract.** Acts as the escrow vault, securely holding funds until released to the rightful recipient.

4. **Backend API.** Coordinates the flow between other components, managing state changes and message updates.

### End-to-End Flow

The complete transfer flow consists of six stages:

1. **Inline Query.** Sender types `@push 10 USDT` in chat; bot suggests transfer options.
2. **Create Check.** Bot posts message with action button; stores record in backend API.
3. **Sign Check.** Sender uses Mini App + TON Connect to sign and send `op::create_check` on-chain.
4. **Store Check.** Smart contract locks funds; backend API polls confirmation and updates message.
5. **Sign Claim.** Recipient signs with Telegram signature and sends external `op::cash_check` message.
6. **Cash Check.** Smart contract verifies signature, releases funds, and deletes check record.

[![The complete sequence of interactions across all system components](https://mytonwallet.io/img/articles/push-technical/figure2.png)](https://mytonwallet.io/img/articles/push-technical/figure2.png)

***[Figure 2](https://mytonwallet.io/img/articles/push-technical/figure2.png).** The complete sequence of interactions across all system components.*

The implementation of each stage will be discussed in more detail in the following sections.

> The current implementation includes several convenience features such as fiat currency conversion, arithmetic operations for bill splitting, and message attachments—details are provided in the Appendix.

### Digital Signatures

The core idea of **@push** is the ability to verify Telegram users' identity on-chain. Since 2024, Telegram supports signing user data in Mini Apps via **public-key cryptography**. Telegram uses **Ed25519**—a state-of-the-art signature scheme, superior to other techniques (such as RSA with SHA-256) in both security and performance. Most importantly, it's also an integral part of the TON blockchain, being **built into TVM** and directly accessible to smart contracts for signature verification.

When a user opens a Mini App, Telegram passes user data (such as ID, username, etc.) to the Mini App along with a digital signature. The signature is created with Telegram's root **private key**, while their **public key** is publicly available on their official website. This enables the Mini App (or associated smart contracts) to securely authenticate the user **without server-side verification**.

Notably, Telegram signs not only the username, but also the **chat instance** where the Mini App was opened. This enables both user authentication and authorization with permissions specific to members of a **given chat**—whether a personal chat, group, or channel. This property enables the implementation of **in-chat crypto transfers**.

> The reliance on Telegram signatures introduces a single external dependency. However, Telegram's operational scale and public key infrastructure make it a significantly more robust trust anchor than smaller custodial operators or exchange-based solutions, while still ensuring that all monetary transactions are resolved on-chain.

### Gasless Claiming

A critical design feature is the ability for recipients to claim checks **without possessing any native coin balance** to cover transaction fees. This is essential for onboarding new users who may not yet have a wallet.

The sender prepays **all gas costs** when creating the check: the cost of check creation, signature verification, and funds withdrawal. The prepaid gas is stored with the check in the smart contract. When the recipient claims, they send a gasless external message that uses the prepaid gas to execute. This enables users to **create a wallet dynamically** and receive their first cryptocurrency transfer without any prior blockchain interaction or deposit.

### Check Binding Mechanisms

**@push** supports two distinct binding mechanisms for checks, each serving different use cases:

- **Chat Instance Binding.** The check is bound to an anonymous chat instance identifier. Any member of the specific chat where the check was created can claim it. The chat instance is an opaque identifier that doesn't reveal the actual chat ID, preserving privacy. This enables transfers within private chats, groups, and channels.

- **Username Binding.** The check is bound to a specific Telegram username (e.g., `@alice`). Only the user holding that username at claim time can receive the transfer. This is useful to ensure the specific user receives it regardless of the chat context.

Both mechanisms rely on Telegram's signature to authenticate the claim, but differ in what data is being verified. The sender selects the binding type when creating the check based on their security and privacy requirements.

### Transfer Direction: Checks and Invoices

While the core architecture remains constant, **@push** supports bidirectional flows that differ in who initiates the transfer:

- **Checks (Sender-Initiated).** The standard flow described throughout this paper. The sender creates and signs the check on-chain, prepaying gas. Any authorized recipient can claim it. This represents the traditional "push" model.

- **Invoices (Recipient-Initiated).** A reverse flow for payment requests. The invoice creator prepares an unsigned check record via the bot, but the actual on-chain `op::create_check` message must be signed by the payer rather than the requester. The smart contract operations remain identical—only the UI flow and signing party differ. This enables payment requests and bill splitting scenarios.

Both directions use the same smart contract, cryptographic verification, and claiming mechanisms.

### Check Creation: Telegram Bot

#### Inline Query Mode

The concept of a "**crypto check message**" on Telegram—a transferable message redeemable for cryptocurrency—has been explored by several existing products. Some implementations leverage the **inline query mode**, a Telegram feature that enables users to interact with bots and Mini Apps while composing a new message.

Users begin typing a message prefixed with the bot username (e.g., `@push 10 USD...`), and the bot responds with multiple suggestions (see [Figure 1](#figure-1)). Users can then select from these suggestions, sending one of the pre-formatted messages to the chat. This mechanism provides an efficient interface for initiating cryptocurrency transfers.

#### Inline Buttons

Once the selected option is sent, the bot receives a `chosen_inline_result` callback. It tells which of the suggestions was selected and also provides the bot with **access to the sent message**—allowing to modify its text, media, and inline buttons. At this point, a `Check` record in the database should be created to save parameters chosen by the user.

Then we can inject an **inline button**—a button below the message allowing for various actions. In our case, it opens a Mini App with a special **start parameter**—the ID of the created check.

Since we can also modify the message text, we update its status to something like *"Waiting for a signature from %username%"*. The bot can also send a **private message** to the user—to remind the check is waiting to be signed.

> Additional implementation details for dynamic cover images are provided in the Appendix 2.

### Check Creation: Signing via Mini App

Some existing services include a private key for each check message in the Mini App button start parameter. While this appears convenient, since any message sent by a bot originates from the backend server, the service provider inevitably has knowledge of the key at creation time. This effectively makes the service **custodial**, as the provider could in principle retain and use the key.

The design of **@push** requires that no sensitive keys be shared with the backend. The check is created **unsigned**, and the actual transfer is sent only via the **client-side Mini App**. The `op::create_check` message is prepared to include the current chat instance or the recipient's username and is then signed with **TON Connect** using any TON-compatible wallet.

> The current **@push** Mini App implementation also leverages Telegram's native UI APIs to achieve a seamless user experience. Details about UI implementation and WebGL graphics are provided in the Appendix 3.

### Check Creation: Backend API

Along with the `op::create_check` on-chain message, a `POST /checks/{check_id}/mark-sending` request is sent to the backend API. The request handler updates the message's text and button to the *"Signing"* status.

The handler also initiates short polling of the smart contract `get_check_info` method, waiting for the new check to appear. When it appears, the on-chain data fields are compared to those stored in the backend database. This comparison is critical to **prevent forgery** by a dishonest sender. Without it, they could substitute essential parameters (such as amount or token) in their on-chain message, while the Telegram message would still display the original values.

If any discrepancies are detected, the message is updated to reflect the actual on-chain parameters. Finally, the status is updated to *"Ready to Receive"*.

### Check Creation: Smart Contract

To create a check, the smart contract expects an **internal** message—either from a sender's wallet for native TON, from one of its own jetton wallets for jetton transfers, or NFT transfer notification messages for NFT checks. Several essential **checks and constraints** are required during this process.

1. **Gas prepayment validation.** Whether it is a native TON, jetton, or NFT transfer, the contract verifies that sufficient TON is attached to cover all gas costs (as described in the Gasless Claiming section). The required TON amount must be included in the initial internal message and will be stored with the check.

2. **Cell depth constraints.** The `in_msg_full.cell_depth()` is limited to the expected depth to constrain pathological inputs.

   > Several wallets build message cells sub-optimally, resulting in unnecessary nesting that exceeds minimal depth. The validation must account for this practical reality.

3. **Storage structure optimization.** The contract storage structure is critical in TON. As the number of stored records grows, dict (`HashmapE`) operations become expensive. To ensure gas costs never exceed the prepaid amount (or the `gas_credit` limit for external messages), we limit the **key size** (K).

   The maximum gas is 100×log2​K for reading and 500×log2​K for writing. Using 20-bit keys instead of conventional 32-bit keys results in fewer possible records (\~1 million vs \~4.3 billion) but lower maximum gas (10,000 vs 16,000 for writing).

   > Due to the Patricia Tree structure underlying TON dicts, sequential (serial) IDs are significantly more cost-effective than random ones.

   An alternative approach would be to use **separate contracts** for each sender or each check. While in some respects more rigorous and scalable, this approach imposes additional overhead and incurs costs associated with contract deployment and orchestration.

4. **Jetton whitelist verification.** For jetton transfers, the sender address must belong to a **whitelist** of owned jetton wallets to ensure genuine transfers and prevent fake jetton spam.

   The whitelist is populated via a special `op::set_acl` operation, accessible only to a privileged *sudoer* address configured in the contract's init data. The whitelist cannot be part of the init data itself due to a circular dependency: jetton wallet addresses are derived from a hash of the contract's code and init data, so the addresses aren't known until after deployment completes.

   > After deployment, the sudoer address calls `op::set_acl` to populate the whitelist with the now-known jetton wallet addresses.

   The whitelist can also distinguish **optimized jettons** (like USDT) that require significantly less gas for transfers, allowing the contract to require lower withdrawal fees for these tokens.

After all validation steps complete successfully, the check data is serialized and stored in the **contract storage**. The stored fields include the asset type (native TON, jetton wallet address, or NFT contract address), the binding the sender chose (chat instance or username), an optional comment, the `created_at` timestamp, and the original sender's address (for authorization of future operations).

### Check Claiming: Mini App

To claim a pending check, the receiver must obtain a **signature** of either their username or the current chat instance. This is accomplished via signed `init_data` provided by Telegram when the user opens the Mini App.

Telegram provides a `prepareSignedPayload` method that allows filtering of unneeded user properties (such as real user ID) from the signature. This enables the check to be handled using only `chat_instance`—an **anonymous context identifier** that does not reveal any information about the real chat ID.

Arbitrary data can be included in the signature. This is essential for signing the **recipient wallet address**. Without this, the external message sent by the receiver could be intercepted by an attacker using public overlays or mempools, enabling withdrawal address tampering.

The address can be passed either to the Mini App `?startapp=` string parameter, or using the `payload` field of the `prepareSignedPayload` method. The method also accepts two useful fields:

- `isPayloadBinary: true`, enabling **binary payload** transmission (in Base64 encoding), avoiding TON address serialization during on-chain verification.

- `shouldSignHash: true`, enabling signing of a **SHA-256 hash** of the data-check string (instead of the data-check string itself), mitigating on-chain message size limitations.

After obtaining the signature, the external `op::cash_check` message is sent directly to the smart contract. It contains the check ID, an authentication timestamp, a username (or chat instance), the receiver address, and the signature itself.

In contrast to the check creation process, this message is **not signed** by the receiver's wallet (it uses the Telegram signature for authentication) and **carries no value** (leveraging the prepaid gas from the sender).

### Check Claiming: Backend API

Along with the external message, a `POST /checks/{check_id}/mark-receiving` request is sent to the backend API. The request handler updates the message's status to *"Receiving"* and initiates short polling of the smart contract to detect when the withdrawal completes.

### Check Claiming: Smart Contract

The on-chain verification of the `op::cash_check` message is the cornerstone of **@push** technology. Several validation steps are required before the escrow smart contract releases the check to the receiver's address.

1. **Signature verification.** First, we calculate a `hash` of the payload message using the format [described](https://core.telegram.org/bots/webapps#validating-data-for-third-party-use) by Telegram using `HASHEXT_SHA256` [TVM instruction](https://docs.ton.org/v3/documentation/tvm/instructions). Then, we evaluate `check_data_signature` function from the FunC [standard library](https://docs.ton.org/v3/documentation/smart-contracts/func/docs/stdlib) providing the hash, the signature and the Telegram public key, which is hardcoded in the contract.

   > The `check_data_signature` function only accepts messages of limited size, making the hashing step essential. Both operations cost approximately 2500 gas units together—critical since all validation must complete within the `gas_credit` limit before the external message is accepted.

2. **Workchain validation.** The receiver address must have `workchain_id == 0`, as the workchain ID is not included in the Telegram signature.

3. **Timestamp validation.** The `auth_date` timestamp must be greater than `created_at` to prevent **replay attacks**. Since Telegram usernames are not soul-bound and chat memberships can change, this prevents users from claiming checks with old signatures obtained before username transfers or chat membership revocations.

4. **Binding verification.** The stored username or chat instance (from check creation) must match the one included in the Telegram signature.

At this point the message is considered **verified**. The contract then **accepts** the message, releases the check to the receiver's address, and deletes the check record.

> **Critical security constraint.** With gasless external messages, accepting the message before validation would allow failed checks to consume gas from the contract balance without deleting the check record. This would enable replay attacks that drain the contract. Therefore, all validation must complete before message acceptance.

### Threat Model

The described security model addresses several attack vectors:

**1. Mempool/Overlay MITM Attacks**

- **Threat**: Attacker intercepts external claim messages from public TON overlays/mempools and replaces recipient address.
- **Mitigation**: The recipient's wallet address is included in the Telegram-signed payload. Any tampering breaks the signature verification on-chain.

**2. Replay Attacks**

- **Threat**: Attacker reuses old signatures to claim checks after username transfer or chat membership changes.
- **Mitigation**: The `auth_date` timestamp must be greater than the check's `created_at`. This prevents old signatures from claiming new checks. Additionally, successful claims delete the check record, preventing re-execution.

**3. Backend Tampering**

- **Threat**: Malicious or compromised bot server attempts to steal funds or forge transfers.
- **Mitigation**: Client-side signing via TON Connect means the server never sees private keys. The API can only update UI state—it cannot move funds or create valid signatures.

**4. Gas Drain DoS Attacks**

- **Threat**: Attacker sends invalid external messages repeatedly to drain contract balance through verification costs.
- **Mitigation**: All validation occurs within the `gas_credit` limit before accepting the message. Failed claims cost nothing to the contract and cannot be replayed after rejection.

**5. Fake Jetton Spam**

- **Threat**: Attacker sends fake jetton transfers to create fraudulent checks.
- **Mitigation**: The smart contract maintains a whitelist of trusted jetton wallet addresses, rejecting transfers from unknown sources.

### Conclusion & Future Directions

**@push** demonstrates that a chat-native cryptocurrency transfer experience can be both smooth and non-custodial. By combining Telegram user interface capabilities with the TON blockchain settlement guarantees, it delivers a user experience equal to custodial services while avoiding their trust liabilities.

The key insights:

- Client-side signing via web interfaces can provide **non-custodial security** without sacrificing UX.
- Modern messaging platforms provide **sufficient cryptographic primitives** for efficient on-chain identity verification.
- External message gas credits enable **gasless claiming** when combined with sender-paid gas prepayment.

Future work may explore:

- **Multi-chain support** via bridge contracts or cross-chain message protocols.
- Reduced Telegram dependency through **additional identity verification** layers (social recovery, biometrics).
- **Batch operations** enabling multiple checks to be created or claimed in a single transaction for improved efficiency.

These insights from **@push** may inform a broader class of applications seeking to merge the **usability** of messaging and social platforms with the **trustlessness** of blockchain systems.

***

## Appendix: Implementation Details

This appendix provides additional technical details about user interface and user experience implementation aspects that, while not central to the cryptographic and architectural innovations, contribute to the overall system quality.

### A1. Additional User-Facing Features

Beyond the core cryptographic and architectural innovations, **@push** includes several convenience features that enhance usability:

- **Fiat Currency Support.** Users can specify amounts in fiat currencies (USD, EUR, CNY, etc.), which are automatically converted to cryptocurrency equivalents at current market rates. The inline preview displays both the crypto amount and fiat equivalent, eliminating the need for manual calculations or external rate lookups.

- **Arithmetic Operations.** The inline query parser supports basic arithmetic expressions (`@push 300/4 EUR`), enabling users to calculate split payments or shared expenses directly within the chat interface without switching to calculator applications.

- **Message Attachments.** Checks support optional text comments and emoji, allowing users to add context or personal messages to their transfers. These messages are stored with the check record and displayed throughout the transfer lifecycle.

- **Notification System.** The bot provides status notifications for check lifecycle events (signed, claimed, received). Users can control notification preferences via the `/mute` command, balancing awareness with notification fatigue.

- **Multi-Wallet Compatibility.** The system integrates with any TON-compatible wallet via TON Connect protocol, avoiding vendor lock-in and enabling users to maintain their existing wallet preferences and security practices.

### A2. Dynamic Cover Image Generation

To render dynamic status text on check message cover images, the system combines pre-rendered SVG templates with programmatic text-to-path conversion using the given font.

The implementation uses [opentype.js](https://www.npmjs.com/package/opentype.js) for font parsing and [makerjs](https://www.npmjs.com/package/makerjs) for SVG path calculation. The following code demonstrates the conversion:

```typescript
import opentype from 'opentype.js';
import makerjs from 'makerjs';
import { JSDOM } from 'jsdom';

const FONT = opentype.loadSync('./fonts/Nunito800.ttf');

export async function textToSvgPath(text: string, options?: Options) {
  const model = new makerjs.models.Text(FONT, text);
  const svg = makerjs.exporter.toSVG(model, options);

  return new JSDOM(svg).window.document.querySelector('path')?.getAttribute('d') ?? '';
}
```

This approach enables dynamic status updates on check images to enhance usability.

### A3. Mini App UI and WebGL Graphics

The Mini App user interface leverages several Telegram APIs to achieve a native look and feel:

- **[System buttons API](https://github.com/mytonwallet-org/mytonwallet/blob/61b45e9/src/push/hooks/useTelegramBottomButton.ts)** for native-style action buttons.
- **[History navigation API](https://github.com/mytonwallet-org/mytonwallet/blob/61b45e9/src/hooks/useHistoryBack.ts#L277)** for seamless back-button integration.
- **[Theme system](https://github.com/mytonwallet-org/mytonwallet/blob/61b45e951321188f96f582e33ce8e110f6814a46/src/util/switchTheme.ts#L59-L61)** respecting user's dark/light mode preferences.
- **[Localization](https://github.com/mytonwallet-org/mytonwallet/blob/61b45e951321188f96f582e33ce8e110f6814a46/src/push/index.tsx#L56-L59)** adapting to user's language settings.

For enhanced visual experience, the token logo is augmented with a [WebGL particle animation](https://github.com/mytonwallet-org/mytonwallet/blob/61b45e9/src/push/util/particles.ts#L445-L547) implementing natural physics. The animation features "flying particles" with burst effects on interaction, running entirely at GPU level. This enables rendering thousands of animated particles simultaneously while maintaining excellent performance even on resource-constrained devices.
