> ## Documentation Index
> Fetch the complete documentation index at: https://tonutils.ness.uz/llms.txt
> Use this file to discover all available pages before exploring further.

# Sign Data

> Request the connected wallet to sign off-chain data and verify the returned signature.

The `signData` method asks the connected wallet to sign arbitrary off-chain content — recording agreement to terms of service, authorizing an off-chain action — and returns an Ed25519 signature you can verify and store; nothing is sent on-chain. All snippets assume a connected `connector` — see [Connect Wallet](/ton-connect/connect-wallet).

## Payload Types

Three payload models are supported:

| Payload | Fields | Use for |
| - | - | - |
| `SignDataPayloadText` | `text` | Human-readable UTF-8 text shown to the user in the wallet UI. |
| `SignDataPayloadBinary` | `raw_bytes` | Opaque binary data. |
| `SignDataPayloadCell` | `tlb_schema`, `cell` | Structured on-chain-style data described by a TL-B schema. |

```python theme={"theme":{"light":"github-light-default","dark":"dark-plus"}}
from ton_core import Cell

from ton_connect.models import (
    SignDataPayloadBinary,
    SignDataPayloadCell,
    SignDataPayloadText,
)

text_payload = SignDataPayloadText(text="I agree to the Terms of Service")
binary_payload = SignDataPayloadBinary(raw_bytes=b"arbitrary bytes")
cell_payload = SignDataPayloadCell(
    tlb_schema="message#_ text:string = Message;",
    cell=Cell.one_from_boc("te6..."),
)
```

Wallets advertise which payload types they support as part of their `SignData` feature. `sign_data()` automatically verifies that the connected wallet declares the feature and supports the payload's type, raising `WalletNotSupportFeatureError` otherwise.

## Requesting a Signature

`sign_data(payload)` sends the request and returns a `request_id` immediately; `wait_sign_data(request_id)` blocks until the wallet responds and returns a `(result, error)` tuple:

```python theme={"theme":{"light":"github-light-default","dark":"dark-plus"}}
from ton_connect.models import SignDataPayloadText

payload = SignDataPayloadText(text="I agree to the Terms of Service")

request_id = await connector.sign_data(payload)
result, error = await connector.wait_sign_data(request_id)

if error:
    print(f"Signing failed: {error}")
else:
    print(f"Signature: {result.signature.as_hex}")
    print(f"Signed at: {result.timestamp}")
```

The result fields:

| Field | Description |
| - | - |
| `signature` | Ed25519 signature (64 bytes); `signature.as_hex` gives the hex string. |
| `address` | Signer wallet address. |
| `timestamp` | Unix timestamp when the wallet produced the signature. |
| `domain` | App domain the wallet signed for. |
| `payload` | The payload the user signed. |

Common errors are `UserRejectsError` and `RequestTimeoutError` — see [Errors](/ton-connect/errors). A pending request can be canceled with `connector.drop_request(request_id)`.

## Verifying the Signature

Never trust a signature without verifying it. Build a `SignDataPayloadDto` from the connected account and the result, then run `VerifySignData`. Any failed check raises `nacl.exceptions.BadSignatureError`:

```python theme={"theme":{"light":"github-light-default","dark":"dark-plus"}}
from nacl.exceptions import BadSignatureError

from ton_connect import VerifySignData
from ton_connect.models import SignDataPayloadDto

try:
    sign_data_payload = SignDataPayloadDto(
        address=connector.account.address,
        network=connector.account.network,
        public_key=connector.account.public_key,
        wallet_state_init=connector.account.state_init,
        signature=result.signature,
        timestamp=result.timestamp,
        domain=result.domain,
        payload=result.payload,
    )
    await VerifySignData(sign_data_payload).verify(
        allowed_domains=["your-app-domain.com"],
        valid_auth_time=5 * 60,  # 5 minutes
    )
    print("SignData verified")
except BadSignatureError as e:
    print(f"SignData verification failed: {e}")
```

Verification performs five cryptographic checks — the full list, the optional `get_wallet_public_key` resolver, and how to verify a signature received from a frontend as a raw dict, are in [Backend Verification](/ton-connect/backend-verification#verifying-signed-data).

## Complete Example

```python theme={"theme":{"light":"github-light-default","dark":"dark-plus"}}
import aiohttp
from nacl.exceptions import BadSignatureError
from ton_core import Address, NetworkGlobalID, PublicKey

from ton_connect import AppWalletsLoader, FileStorage, TonConnect, VerifySignData
from ton_connect.models import (
    SignDataPayloadDto,
    SignDataPayloadText,
)

# Setup constants — see Installation and Initialization
MANIFEST_URL = "https://raw.githubusercontent.com/nessshon/ton-connect/main/assets/tonconnect-manifest.json"
SESSION_KEY = "user-123"
STORAGE_PATH = "./tonconnect-storage.json"

# Domain your app is served from — must match your tonconnect-manifest.json
APP_DOMAIN = "raw.githubusercontent.com"

# Toncenter API for the network the example connects to (testnet)
TONCENTER_API = "https://testnet.toncenter.com/api/v2"

# Text the user will be asked to sign in their wallet UI
SIGN_TEXT = "I agree to the Terms of Service"


async def get_wallet_public_key(address: Address) -> PublicKey | None:
    # Current key from the get_public_key get-method; None falls back to the initial key
    body = {"address": address.to_str(), "method": "get_public_key", "stack": []}
    timeout = aiohttp.ClientTimeout(total=15)
    async with (
        aiohttp.ClientSession(timeout=timeout) as session,
        session.post(f"{TONCENTER_API}/runGetMethod", json=body) as resp,
    ):
        if not resp.ok:
            return None
        result = (await resp.json())["result"]
    if result["exit_code"] != 0 or not result["stack"]:
        return None
    return PublicKey(result["stack"][0][1])


async def main() -> None:
    storage = FileStorage(STORAGE_PATH)
    app_wallets_loader = AppWalletsLoader(require_sign_data=True)

    tc = TonConnect(
        storage=storage,
        manifest_url=MANIFEST_URL,
        app_wallets=app_wallets_loader.get_wallets(),
    )
    connector = tc.create_connector(SESSION_KEY)

    # Try to restore a previously saved connection before starting a new one
    restored = await connector.restore()

    if not restored:
        request = connector.make_connect_request()
        await connector.connect(
            request=request,
            network=NetworkGlobalID.TESTNET,
        )

        for app_wallet in app_wallets_loader.get_wallets():
            print(f"{app_wallet.name}: {connector.make_connect_url(request, app_wallet)}")

        wallet, error = await connector.wait_connect()

        if error:
            print(f"Connection failed: {error}")
            return

    address = connector.account.address.to_str(is_bounceable=False)
    print(f"Address: {address}")

    # Build the sign data payload
    payload = SignDataPayloadText(text=SIGN_TEXT)

    # Send the sign data request to the connected wallet
    # Returns a request_id immediately — the user approves in their wallet app
    request_id = await connector.sign_data(payload)
    print("Sign request sent, waiting for confirmation...")

    # Block until the wallet signs the data (or rejects/times out)
    result, error = await connector.wait_sign_data(request_id)

    if error:
        print(f"Signing failed: {error}")
        return

    print(f"Signature: {result.signature.as_hex}")
    print(f"Signed at: {result.timestamp}")

    try:
        # Verify the signature returned by the wallet
        sign_data_payload = SignDataPayloadDto(
            address=connector.account.address,
            network=connector.account.network,
            public_key=connector.account.public_key,
            wallet_state_init=connector.account.state_init,
            signature=result.signature,
            timestamp=result.timestamp,
            domain=result.domain,
            payload=result.payload,
        )
        # get_wallet_public_key: optional, checks the Telegram wallet against its current key
        await VerifySignData(sign_data_payload).verify(
            allowed_domains=[APP_DOMAIN],
            valid_auth_time=5 * 60,  # 5 minutes
            get_wallet_public_key=get_wallet_public_key,
        )
        print("SignData verified")
    except BadSignatureError as e:
        print(f"SignData verification failed: {e}")

    # Close all active bridge connections
    await tc.close_all()


if __name__ == "__main__":
    import asyncio

    asyncio.run(main())
```

<Card title="Send Request Examples" icon="github" href="https://github.com/nessshon/ton-connect/tree/main/examples/send_request">
  Full source code of the sign data flow on GitHub
</Card>


## Related topics

- [Backend Verification](/ton-connect/backend-verification.md)
- [TON Connect](/ton-connect/index.md)
- [Errors](/ton-connect/errors.md)
- [Event Handling](/ton-connect/event-handling.md)
- [TON Connect Installation and Initialization](/ton-connect/installation-and-initialization.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.