From 94e99b25328c2f7c440b2539346cc8a7177f48cd Mon Sep 17 00:00:00 2001 From: SwiftAdviser Date: Mon, 7 Aug 2023 15:49:43 +0200 Subject: [PATCH] added ton-connect documentation into docs --- docs/develop/dapps/ton-connect/integration.md | 25 +- .../dapps/ton-connect/protocol/README.md | 53 ++ .../dapps/ton-connect/protocol/bridge.md | 202 +++++++ .../protocol/requests-responses.md | 493 ++++++++++++++++++ .../dapps/ton-connect/protocol/session.md | 62 +++ .../{ => protocol}/wallet-guidelines.md | 4 +- .../ton-connect/{ => protocol}/workflow.md | 24 +- sidebars.js | 13 +- 8 files changed, 838 insertions(+), 38 deletions(-) create mode 100644 docs/develop/dapps/ton-connect/protocol/README.md create mode 100644 docs/develop/dapps/ton-connect/protocol/bridge.md create mode 100644 docs/develop/dapps/ton-connect/protocol/requests-responses.md create mode 100644 docs/develop/dapps/ton-connect/protocol/session.md rename docs/develop/dapps/ton-connect/{ => protocol}/wallet-guidelines.md (97%) rename docs/develop/dapps/ton-connect/{ => protocol}/workflow.md (66%) diff --git a/docs/develop/dapps/ton-connect/integration.md b/docs/develop/dapps/ton-connect/integration.md index f3e0ea7775..c95bf922b8 100644 --- a/docs/develop/dapps/ton-connect/integration.md +++ b/docs/develop/dapps/ton-connect/integration.md @@ -4,8 +4,8 @@ In this tutorial, we’ll create a sample web app that supports TON Connect 2.0 ## Documentation links -1. [TON Connect SDK documentation](https://www.npmjs.com/package/@tonconnect/sdk) -2. [Wallet-application message exchange protocol](https://github.com/ton-connect/docs/blob/main/requests-responses.md), includes manifest format. +1. [@tonconnect/sdk documentation](https://www.npmjs.com/package/@tonconnect/sdk) +2. [Wallet-application message exchange protocol](https://github.com/ton-connect/docs/blob/main/requests-responses.md) 3. [Tonkeeper implementation of wallet side](https://github.com/tonkeeper/wallet/tree/main/src/tonconnect) ## Prerequisites @@ -14,7 +14,7 @@ In order for connectivity to be fluent between apps and wallets, the web app mus ## Getting wallets support list -To increase the overall adoption of TON Blockchain, it is necessary that TON Connect 2.0 is able to facilitate a vast number of application and wallet connectivity integrations. Of late and of significant importance, the ongoing development of TON Connect 2.0 has allowed for the connection of the Tonkeeper and OpenMask wallets with various TON Ecosystem Apps. It is our mission to eventually allow for the exchange of data between applications and all wallet types built on TON via the TON Connect protocol. For now, this is realized by providing the ability for TON Connect to load an extensive list of available wallets currently operating within the TON Ecosystem. +To increase the overall adoption of TON Blockchain, it is necessary that TON Connect 2.0 is able to facilitate a vast number of application and wallet connectivity integrations. Of late and of significant importance, the ongoing development of TON Connect 2.0 has allowed for the connection of the Tonkeeper, TonHub, MyTonWallet and other wallets with various TON Ecosystem Apps. It is our mission to eventually allow for the exchange of data between applications and all wallet types built on TON via the TON Connect protocol. For now, this is realized by providing the ability for TON Connect to load an extensive list of available wallets currently operating within the TON Ecosystem. At the moment our sample web app enables the following: @@ -46,7 +46,7 @@ For learning purposes, let's take a looks at the HTML page described by the foll If you load this page in browser and look into console, you may get something like that: -```js +```bash > Array [ {…}, {…} ] 0: Object { name: "Tonkeeper", imageUrl: "https://tonkeeper.com/assets/tonconnect-icon.png", aboutUrl: "https://tonkeeper.com", … } @@ -60,15 +60,6 @@ If you load this page in browser and look into console, you may get something li name: "Tonkeeper" tondns: "tonkeeper.ton" universalLink: "https://app.tonkeeper.com/ton-connect" - -1: Object { name: "OpenMask", imageUrl: "https://raw.githubusercontent.com/OpenProduct/openmask-extension/main/public/openmask-logo-288.png", aboutUrl: "https://www.openmask.app/", … } - aboutUrl: "https://www.openmask.app/" - embedded: false - imageUrl: "https://raw.githubusercontent.com/OpenProduct/openmask-extension/main/public/openmask-logo-288.png" - injected: false - jsBridgeKey: "openmask" - name: "OpenMask" - tondns: undefined ``` According to TON Connect 2.0 specifications, wallet app information always makes use of the following format: @@ -240,11 +231,7 @@ Upon clicking the mobile phone link, Tonkeeper automatically opens and then clos `Error: [TON_CONNECT_SDK_ERROR] Can't get null/tonconnect-manifest.json`. This means the application manifest must be available for download. - -### Logging in with OpenMask - -OpenMask didn't inject its information in the window, so connecting with it failed. The most probable reason is because a local page for the web app was used. - + ## Connection with using app manifest Starting from this point forward, it is necessary to host user files (mostly tonconnect-manifest.json) somewhere. In this instance we’ll use the manifest from another web application. This however is not recommended for production environments, but allowed for testing purposes. @@ -292,7 +279,7 @@ Therefore, the user is able to accept the same login request if the link is save Afterwards, the login request is accepted and is immediately reflected in the browser console as follows: -```js +```bash 22:40:13.887 Connection status: Object { device: {…}, provider: "http", account: {…} } account: Object { address: "0:b2a1ec...", chain: "-239", walletStateInit: "te6cckECFgEAAwQAAgE0ARUBFP8A9..." } diff --git a/docs/develop/dapps/ton-connect/protocol/README.md b/docs/develop/dapps/ton-connect/protocol/README.md new file mode 100644 index 0000000000..4e9124e3b7 --- /dev/null +++ b/docs/develop/dapps/ton-connect/protocol/README.md @@ -0,0 +1,53 @@ +# Protocol specifications + +## Who is this section for? + +- If you implement an SDK +- If you implement a wallet +- If you want to learn how TON Connect works + +## Sections overview + +* [Protocol workflow](/develop/dapps/ton-connect/protocol/workflow): an overview of all the protocols. +* [Bridge API](/develop/dapps/ton-connect/protocol/bridge) specifies how the data is transmitted between the app and the wallet. +* [Session protocol](/develop/dapps/ton-connect/protocol/session) ensures end-to-end encrypted communication over the bridge. +* [Requests protocol](/develop/dapps/ton-connect/protocol/requests-responses) defines requests and responses for the app and the wallet. +* [Wallet guidelines](/develop/dapps/ton-connect/protocol/wallet-guidelines) defines guidelines for wallet developers. + +## FAQ + +#### I am building an HTML/JS app, what should I read? + +Simply use the [Supported SDKs](/develop/dapps/ton-connect/developers) and do not worry about the underlying protocols. + +#### I need an SDK in my favorite language + +Please take the [JS SDK](/develop/dapps/ton-connect/developers) as a reference and check out the protocol docs above. + +#### How do you detect whether the app is embedded in the wallet? + +JS SDK does that for you; just get wallets list `connector.getWallets()` and check `embedded` property of the corresponding list item. If you build your own SDK you should check `window.[targetWalletJsBridgeKey].tonconnect.isWalletBrowser`. + +#### How do you detect if the wallet is a browser extension? + +Like with embedded apps (see above), JS SDK detects it for you via `injected` property of the corresponding `connector.getWallets()` list item. If you build your own SDK you should check that `window.[targetWalletJsBridgeKey].tonconnect` exists. + +#### How to implement backend authorization with tonconnect? + +[See an example of dapp-backend](https://github.com/ton-connect/demo-dapp-backend) + +#### How do I make my own bridge? + +You don’t need to, unless you are building a wallet. + +If you build a wallet, you will need to provide a bridge. See our [reference implementation in Go](https://github.com/ton-connect/bridge). + +Keep in mind that the wallet’s side of the bridge API is not mandated. + +For a quick start you can use the common TON Connect bridge https://bridge.tonapi.io/bridge. + +#### I make a wallet, how do I add it to the list of wallets? + +Submit a pull request for the [wallets-list](https://github.com/ton-blockchain/wallets-list) repository and fill our all the necessary metadata. + +Apps may also add wallets directly through the SDK. \ No newline at end of file diff --git a/docs/develop/dapps/ton-connect/protocol/bridge.md b/docs/develop/dapps/ton-connect/protocol/bridge.md new file mode 100644 index 0000000000..8478e9a37e --- /dev/null +++ b/docs/develop/dapps/ton-connect/protocol/bridge.md @@ -0,0 +1,202 @@ +# Bridge API + +Bridge is a transport mechanism to deliver messages from the app to the wallet and vice versa. + +* **Bridge is maintained by the wallet provider**. App developers do not have to choose or build a bridge. Each wallet’s bridge is listed in the [wallets-list](https://github.com/ton-blockchain/wallets-list) config. +* **Messages are end-to-end encrypted.** Bridge does not see the contents or long-term identifiers of the app or wallet. +* **Communication is symmetrical.** Bridge does not distinguish between apps and wallets: both are simply clients. +* Bridge keeps separate queues of messages for each recipient’s **Client ID**. + +Bridge comes in two flavors: + +- [HTTP Bridge](#http-bridge): for the external apps and services. +- [JS Bridge](#js-bridge): for apps opened within the wallet; or when the wallet is a browser extension. + +## HTTP Bridge + +Client with ID **A** connects to the bridge to listen to incoming requests. + +**Client ID is semi-private:** apps and wallets are not supposed to share their IDs with other entities to avoid having their messages removed unexpectedly. + +**Client can subscribe on few Client IDs** - in this case it should enumerate IDs separated with commas. For example: `?client_id=,,` + +```tsx +request + GET /events?client_id= + + Accept: text/event-stream +``` + +**Subscribing to the bridge second (any other) time** + +```tsx +request + GET /events?client_id=&last_event_id= + + Accept: text/event-stream +``` + +**lastEventId** – the eventId of the last SSE event wallet got over the bridge. In this case wallet will fetch all the events which happened after the last connection. + +Sending message from client A to client B. Bridge returns error if ttl is too high. + +```tsx +request + POST /message?client_id=?to=&ttl=300&topic= + + body: +``` + + +The `topic` [optional] query parameter can be used by the bridge to deliver the push notification to the wallet. If the parameter is given, it must correspond to the RPC method called inside the encrypted `message`. + +Bridge buffers messages up to TTL (in secs), but removes them as soon as the recipient receives the message. + +If the TTL exceeds the hard limit of the bridge server, it should respond with HTTP 400. Bridges should support at least 300 seconds TTL. + +When the bridge receives a message `base64_encoded_message` from client `A` addressed to client `B`, it generates a message `BridgeMessage`: + +```js +{ + "from": , + "message": +} +``` + +and sends it to the client B via SSE connection +```js +resB.write(BridgeMessage) +``` + +### Heartbeat + +To keep the connection, bridge server should periodically send a "heartbeat" message to the SSE channel. Client should ignore such messages. +So, the bridge heartbeat message is a string with word `heartbeat`. + + +## Universal link + +When the app initiates the connection it sends it directly to the wallet via the QR code or a universal link. + +```bash +https://? + v=2& + id=& + r=& + ret=back +``` + +Parameter **v** specifies the protocol version. Unsupported versions are not accepted by the wallets. + +Parameter **id** specifies app’s Client ID encoded as hex (without '0x' prefix). + +Parameter **r** specifies URL-safe json [ConnectRequest](/develop/dapps/ton-connect/protocol/requests-responses#initiating-connection). + +Parameter **ret** (optional) specifies return strategy for the deeplink when user signs/declines the request. +- 'back' (default) means return to the app which initialized deeplink jump (e.g. browser, native app, ...), +- 'none' means no jumps after user action; +- a URL: wallet will open this URL after completing the user's action. Note, that you shouldn't pass your app's URL if it is a webpage. This option should be used for native apps to work around possible OS-specific issues with `'back'` option. + +`ret` parameter should be supported for empty deeplinks -- it might be used to specify the wallet behavior after other actions confirmation (send transaction, sign raw, ...). +```bash +https://?ret=back +``` + + +The link may be embedded in a QR code or clicked directly. + +The initial request is unencrypted because (1) there is no personal data being communicated yet, (2) app does not even know the identity of the wallet. + +### Unified deeplink `tc` +In addition to its own universal link, the wallet must support the unified deeplink. + +This allows applications to create a single qr code, which can be used to connect to any wallet. + +More specifically, the wallet must support `tc://` deeplink as well as its own ``. + +Therefore, the following `connect request` must be processed by the wallet: + +```bash +tc://? + v=2& + id=& + r=& + ret=back +``` + + +## JS bridge + +Used by the embedded apps via the injected binding `window..tonconnect`. + +`wallet-js-bridge-key` can be specified in the [wallets list](https://github.com/ton-blockchain/wallets-list) + +JS bridge runs on the same device as the wallet and the app, so communication is not encrypted. + +The app works directly with plaintext requests and responses, without session keys and encryption. + +```tsx +interface TonConnectBridge { + deviceInfo: DeviceInfo; // see Requests/Responses spec + walletInfo?: WalletInfo; + protocolVersion: number; // max supported Ton Connect version (e.g. 2) + isWalletBrowser: boolean; // if the page is opened into wallet's browser + connect(protocolVersion: number, message: ConnectRequest): Promise; + restoreConnection(): Promise; + send(message: AppRequest): Promise; + listen(callback: (event: WalletEvent) => void): () => void; +} +``` + +Just like with the HTTP bridge, wallet side of the bridge does not receive the app requests except for [ConnectRequest](/develop/dapps/ton-connect/protocol/requests-responses#initiating-connection) until the session is confirmed by the user. Technically, the messages arrive from the webview into the bridge controller, but they are silently ignored. + +SDK around the implements **autoconnect()** and **connect()** as silent and non-silent attempts at establishing the connection. + +### walletInfo (optional) +Represents wallet metadata. Might be defined to make an injectable wallet works with TonConnect even if the wallet is not listed in the [wallets-list.json](https://github.com/ton-blockchain/wallets-list). + +Wallet metadata format: +```ts +interface WalletInfo { + name: string; + image: ; + tondns?: string; + about_url: ; +} +``` + +Detailed properties description: https://github.com/ton-blockchain/wallets-list#entry-format. + +If `TonConnectBridge.walletInfo` is defined and the wallet is listed in the [wallets-list.json](https://github.com/ton-blockchain/wallets-list), `TonConnectBridge.walletInfo` properties will override corresponding wallet properties from the wallets-list.json. + + +### connect() + +Initiates connect request, this is analogous to QR/link when using the HTTP bridge. + +If the app was previously approved for the current account — connects silently with ConnectEvent. Otherwise shows confirmation dialog to the user. + +You shouldn't use the `connect` method without explicit user action (e.g. connect button click). If you want automatically try to restore previous connection, you should use the `restoreConnection` method. + +### restoreConnection() + +Attempts to restore the previous connection. + +If the app was previously approved for the current account — connects silently with the new `ConnectEvent` with only a `ton_addr` data item. + + +Otherwise returns `ConnectEventError` with error code 100 (Unknown app). + + +### send() + +Sends a [message](/develop/dapps/ton-connect/protocol/requests-responses#messages) to the bridge, excluding the ConnectRequest (that goes into QR code when using HTTP bridge and into connect when using JS Bridge). +Directly returns promise with WalletResponse, do you don't need to wait for responses with `listen`; + +### listen() + +Registers a listener for events from the wallet. + +Returns unsubscribe function. + +Currently, only `disconnect` event is available. Later there will be a switch account event and other wallet events. \ No newline at end of file diff --git a/docs/develop/dapps/ton-connect/protocol/requests-responses.md b/docs/develop/dapps/ton-connect/protocol/requests-responses.md new file mode 100644 index 0000000000..a8f141647e --- /dev/null +++ b/docs/develop/dapps/ton-connect/protocol/requests-responses.md @@ -0,0 +1,493 @@ +# Requests and Responses + +App sends requests to the wallet. Wallet sends responses and events to the app. + +```tsx +type AppMessage = ConnectRequest | AppRequest; + +type WalletMessage = WalletResponse | WalletEvent; +``` + +### App manifest +App needs to have its manifest to pass meta information to the wallet. Manifest is a JSON file named as `tonconnect-manifest.json` following format: + +```json +{ + "url": "", // required + "name": "", // required + "iconUrl": "", // required + "termsOfUseUrl": "", // optional + "privacyPolicyUrl": "" // optional +} +``` + +Best practice is to place the manifest in the root of your app, e.g. `https://myapp.com/tonconnect-manifest.json`. It allows the wallet to handle your app better and improve the UX connected to your app. +Make sure that manifest is available to GET by its URL. + +#### Fields description +- `url` -- app URL. Will be used as the dapp identifier. Will be used to open the dapp after click to its icon in the wallet. It is recommended to pass url without closing slash, e.g. 'https://mydapp.com' instead of 'https://mydapp.com/'. +- `name` -- app name. Might be simple, will not be used as identifier. +- `iconUrl` -- Url to the app icon. Must be PNG, ICO, ... format. SVG icons are not supported. Perfectly pass url to a 180x180px PNG icon. +- `termsOfUseUrl` -- (optional) url to the Terms Of Use document. Optional for usual apps, but required for the apps which is placed in the Tonkeeper recommended apps list. +- `privacyPolicyUrl` -- (optional) url to the Privacy Policy document. Optional for usual apps, but required for the apps which is placed in the Tonkeeper recommended apps list. + +### Initiating connection + +App’s request message is **InitialRequest**. + +```tsx +type ConnectRequest = { + manifestUrl: string; + items: ConnectItem[], // data items to share with the app +} + +// In the future we may add other personal items. +// Or, instead of the wallet address we may ask for per-service ID. +type ConnectItem = TonAddressItem | TonProofItem | ...; + +type TonAddressItem = { + name: "ton_addr"; +} +type TonProofItem = { + name: "ton_proof"; + payload: string; // arbitrary payload, e.g. nonce + expiration timestamp. +} +``` + +ConnectRequest description: +- `manifestUrl`: link to the app's tonconnect-manifest.json +- `items`: data items to share with the app. + +Wallet responds with **ConnectEvent** message if the user approves the request. + +```tsx +type ConnectEvent = ConnectEventSuccess | ConnectEventError; + +type ConnectEventSuccess = { + event: "connect"; + id: number; // increasing event counter + payload: { + items: ConnectItemReply[]; + device: DeviceInfo; + } +} +type ConnectEventError = { + event: "connect_error", + id: number; // increasing event counter + payload: { + code: number; + message: string; + } +} + +type DeviceInfo = { + platform: "iphone" | "ipad" | "android" | "windows" | "mac" | "linux"; + appName: string; // e.g. "Tonkeeper" + appVersion: string; // e.g. "2.3.367" + maxProtocolVersion: number; + features: Feature[]; // list of supported features and methods in RPC + // Currently there is only one feature -- 'SendTransaction'; +} + +type Feature = { name: 'SendTransaction', maxMessages: number } | // `maxMessages` is maximum number of messages in one `SendTransaction` that the wallet supports + { name: 'SignData' }; + +type ConnectItemReply = TonAddressItemReply | TonProofItemReply ...; + +// Untrusted data returned by the wallet. +// If you need a guarantee that the user owns this address and public key, you need to additionally request a ton_proof. +type TonAddressItemReply = { + name: "ton_addr"; + address: string; // TON address raw (`0:`) + network: NETWORK; // network global_id + publicKey: string; // HEX string without 0x + walletStateInit: string; // Base64 (not url safe) encoded stateinit cell for the wallet contract +} + +type TonProofItemReply = TonProofItemReplySuccess | TonProofItemReplyError; + +type TonProofItemReplySuccess = { + name: "ton_proof"; + proof: { + timestamp: string; // 64-bit unix epoch time of the signing operation (seconds) + domain: { + lengthBytes: number; // AppDomain Length + value: string; // app domain name (as url part, without encoding) + }; + signature: string; // base64-encoded signature + payload: string; // payload from the request + } +} + +type TonProofItemReplyError = { + name: "ton_addr"; + error: { + code: ConnectItemErrorCode; + message?: string; + } +} + +enum NETWORK { + MAINNET = '-239', + TESTNET = '-3' +} +``` + +**Connect event error codes:** + +| code | description | +|------|------------------------------| +| 0 | Unknown error | +| 1 | Bad request | +| 2 | App manifest not found | +| 3 | App manifest content error | +| 100 | Unknown app | +| 300 | User declined the connection | + +**Connect item error codes:** + +| code | description | +|------|------------------------------| +| 0 | Unknown error | +| 400 | Method is not supported | + +If the wallet doesn't support the requested `ConnectItem` (e.g. "ton_proof"), it must send reply with the following ConnectItemReply corresponding to the requested item. +with following structure: +```ts +type ConnectItemReplyError = { + name: ""; + error: { + code: 400; + message?: string; + } +} +``` + +### Address proof signature (`ton_proof`) + +If `TonProofItem` is requested, wallet proves ownership of the selected account’s key. The signed message is bound to: + +- Unique prefix to separate messages from on-chain messages. (`ton-connect`) +- Wallet address. +- App domain +- Signing timestamp +- App’s custom payload (where server may put its nonce, cookie id, expiration time). + +``` +message = utf8_encode("ton-proof-item-v2/") ++ + Address ++ + AppDomain ++ + Timestamp ++ + Payload +signature = Ed25519Sign(privkey, sha256(0xffff ++ utf8_encode("ton-connect") ++ sha256(message))) +``` + +where: + +* `Address` is the wallet address encoded as a sequence: + * `workchain`: 32-bit signed integer big endian; + * `hash`: 256-bit unsigned integer big endian; +* `AppDomain` is Length ++ EncodedDomainName + - `Length` is 32-bit value of utf-8 encoded app domain name length in bytes + - `EncodedDomainName` id `Length`-byte utf-8 encoded app domain name +* `Timestamp` 64-bit unix epoch time of the signing operation +* `Payload` is a variable-length binary string. + +Note: payload is variable-length untrusted data. To avoid using unnecessary length prefixes we simply put it last in the message. + +The signature must be verified by public key: + +1. First, try to obtain public key via `get_public_key` get-method on smart contract deployed at `Address`. + +2. If the smart contract is not deployed yet, or the get-method is missing, you need: + + 2.1. Parse `TonAddressItemReply.walletStateInit` and get public key from stateInit. You can compare the `walletStateInit.code` with the code of standard wallets contracts and parse the data according to the found wallet version. + + 2.2. Check that `TonAddressItemReply.publicKey` equals to obtained public key + + 2.3. Check that `TonAddressItemReply.walletStateInit.hash()` equals to `TonAddressItemReply.address`. `.hash()` means BoC hash. + +## Messages + +- All messages from the app to the wallet are requests for an operation. +- Messages from the wallet to the application can be either responses to app requests or events triggered by user actions on the side of the wallet. + +**Available operations:** + +- sendTransaction +- signData +- disconnect + +**Available events:** + +- connect +- connect_error +- disconnect + +### Structure + +**All app requests have the following structure (like json-rpc 2.0)** +```tsx +interface AppRequest { + method: string; + params: string[]; + id: string; +} +``` +Where +- `method`: name of the operation ('sendTransaction', 'signMessage', ...) +- `params`: array of the operation specific parameters +- `id`: increasing identifier that allows to match requests and responses + + +**Wallet messages are responses or events.** + +Response is an object formatted as a json-rpc 2.0 response. Response `id` must match request's id. + +Wallet doesn't accept any request with an id that does not greater the last processed request id of that session. + +```tsx +type WalletResponse = WalletResponseSuccess | WalletResponseError; + +interface WalletResponseSuccess { + result: string; + id: string; +} + +interface WalletResponseError { + error: { code: number; message: string; data?: unknown }; + id: string; +} +``` + +Event is an object with property `event` that is equal to event's name, `id` that is increasing event counter (**not** related to `request.id` because there is no request for an event), and `payload` that contains event additional data. +```tsx +interface WalletEvent { + event: WalletEventName; + id: number; // increasing event counter + payload: ; // specific payload for each event +} + +type WalletEventName = 'connect' | 'connect_error' | 'disconnect'; +``` + +Wallet must increase `id` while generating a new event. (Every next event must have `id` > previous event `id`) + +DApp doesn't accept any event with an id that does not greater the last processed event id of that session. + +### Methods + +#### Sign and send transaction + +App sends **SendTransactionRequest**: + +```tsx +interface SendTransactionRequest { + method: 'sendTransaction'; + params: []; + id: string; +} +``` + +Where `` is JSON with following properties: + +* `valid_until` (integer, optional): unix timestamp. after th moment transaction will be invalid. +* `network` (NETWORK, optional): The network (mainnet or testnet) where DApp intends to send the transaction. If not set, the transaction is sent to the network currently set in the wallet, but this is not safe and DApp should always strive to set the network. If the `network` parameter is set, but the wallet has a different network set, the wallet should show an alert and DO NOT ALLOW TO SEND this transaction. +* `from` (string in `:` format, optional) - The sender address from which DApp intends to send the transaction. If not set, wallet allows user to select the sender's address at the moment of transaction approval. If `from` parameter is set, the wallet should DO NOT ALLOW user to select the sender's address; If sending from the specified address is impossible, the wallet should show an alert and DO NOT ALLOW TO SEND this transaction. +* `messages` (array of messages): 1-4 outgoing messages from the wallet contract to other accounts. All messages are sent out in order, however **the wallet cannot guarantee that messages will be delivered and executed in same order**. + +Message structure: +* `address` (string): message destination +* `amount` (decimal string): number of nanocoins to send. +* `payload` (string base64, optional): raw one-cell BoC encoded in Base64. +* `stateInit` (string base64, optional): raw once-cell BoC encoded in Base64. + +#### Common cases +1. No payload, no stateInit: simple transfer without a message. +2. payload is prefixed with 32 zero bits, no stateInit: simple transfer with a text message. +3. No payload or prefixed with 32 zero bits; stateInit is present: deployment of the contract. + +
+Example + +```json5 +{ + "valid_until": 1658253458, + "network": "-239", // enum NETWORK { MAINNET = '-239', TESTNET = '-3'} + "from": "0:348bcf827469c5fc38541c77fdd91d4e347eac200f6f2d9fd62dc08885f0415f", + "messages": [ + { + "address": "0:412410771DA82CBA306A55FA9E0D43C9D245E38133CB58F1457DFB8D5CD8892F", + "amount": "20000000", + "stateInit": "base64bocblahblahblah==" //deploy contract + },{ + "address": "0:E69F10CC84877ABF539F83F879291E5CA169451BA7BCE91A37A5CED3AB8080D3", + "amount": "60000000", + "payload": "base64bocblahblahblah==" //transfer nft to new deployed account 0:412410771DA82CBA306A55FA9E0D43C9D245E38133CB58F1457DFB8D5CD8892F + } + ] +} +``` +
+ + +Wallet replies with **SendTransactionResponse**: + +```tsx +type SendTransactionResponse = SendTransactionResponseSuccess | SendTransactionResponseError; + +interface SendTransactionResponseSuccess { + result: ; + id: string; + +} + +interface SendTransactionResponseError { + error: { code: number; message: string }; + id: string; +} +``` + +**Error codes:** + +| code | description | +|------|-------------------------------| +| 0 | Unknown error | +| 1 | Bad request | +| 100 | Unknown app | +| 300 | User declined the transaction | +| 400 | Method not supported | + + +#### Sign Data (Experimental) + +> WARNING: this is currently an experimental method and its signature may change in the future + +App sends **SignDataRequest**: + +```tsx +interface SignDataRequest { + method: 'signData'; + params: []; + id: string; +} +``` + +Where `` is JSON with following properties: + +* `schema_crc` (integer): indicates the layout of payload cell that in turn defines domain separation. +* `cell` (string, base64 encoded Cell): contains arbitrary data per its TL-B definition. +* `publicKey` (HEX string without 0x, optional): The public key of key pair from which DApp intends to sign the data. If not set, the wallet is not limited in what keypair to sign. If `publicKey` parameter is set, the wallet SHOULD to sign by keypair corresponding this public key; If sign by this specified keypair is impossible, the wallet should show an alert and DO NOT ALLOW TO SIGN this data. + +The signature will be computed in the following way: +`ed25519(uint32be(schema_crc) ++ uint64be(timestamp) ++ cell_hash(X), privkey)` + +[See details](https://github.com/oleganza/TEPs/blob/datasig/text/0000-data-signatures.md) + +Wallet should decode the cell in accordance with the schema_crc and show corresponding data to the user. +If the schema_crc is unknown to the wallet, the wallet should show danger notification/UI to the user. + +Wallet replies with **SignDataResponse**: + +```tsx +type SignDataResponse = SignDataResponseSuccess | SignDataResponseError; + +interface SignDataResponseSuccess { + result: { + signature: string; // base64 encoded signature + timestamp: string; // UNIX timestamp in seconds (UTC) at the moment on creating the signature. + }; + id: string; +} + +interface SignDataResponseError { + error: { code: number; message: string }; + id: string; +} +``` + +**Error codes:** + +| code | description | +|------|---------------------------| +| 0 | Unknown error | +| 1 | Bad request | +| 100 | Unknown app | +| 300 | User declined the request | +| 400 | Method not supported | + + +#### Disconnect operation +When user disconnects the wallet in the dApp, dApp should inform the wallet to help the wallet save resources and delete unnecessary session. +Allows the wallet to update its interface to the disconnected state. + +```tsx +interface DisconnectRequest { + method: 'disconnect'; + params: []; + id: string; +} +``` + +Wallet replies with **DisconnectResponse**: + +```ts +type DisconnectResponse = DisconnectResponseSuccess | DisconnectResponseError; + +interface DisconnectResponseSuccess { + id: string; + result: { }; +} + +interface DisconnectResponseError { + error: { code: number; message: string }; + id: string; +} +``` + +Wallet **shouldn't** emit a 'Disconnect' event if disconnect was initialized by the dApp. + +**Error codes:** + +| code | description | +|------|---------------------------| +| 0 | Unknown error | +| 1 | Bad request | +| 100 | Unknown app | +| 400 | Method not supported | + + +### Wallet events + +Disconnect + +The event fires when the user deletes the app in the wallet. The app must react to the event and delete the saved session. If the user disconnects the wallet on the app side, then the event does not fire, and the session information remains in the localstorage + +```tsx +interface DisconnectEvent { + type: "disconnect", + id: number; // increasing event counter + payload: { } +} +``` + +Connect + +```tsx +type ConnectEventSuccess = { + event: "connect", + id: number; // increasing event counter + payload: { + items: ConnectItemReply[]; + device: DeviceInfo; + } +} +type ConnectEventError = { + event: "connect_error", + id: number; // increasing event counter + payload: { + code: number; + message: string; + } +} +``` \ No newline at end of file diff --git a/docs/develop/dapps/ton-connect/protocol/session.md b/docs/develop/dapps/ton-connect/protocol/session.md new file mode 100644 index 0000000000..a85fb87d6b --- /dev/null +++ b/docs/develop/dapps/ton-connect/protocol/session.md @@ -0,0 +1,62 @@ +# Session protocol + +Session protocol defines client identifiers and offers end-to-end encryption for the app and the wallet. This means the HTTP bridge is fully untrusted and cannot read the user’s data transmitted between the app and the wallet. JS bridge does not use this protocol since both the wallet and the app run on the same device. + +## Definitions + +### Client Keypair + +X25519 keypair for the use with NaCl “crypto_box” protocol. + +``` +a <- random 23 bytes +A <- X25519Pubkey(s) +``` + +or + +``` +(a,A) <- nacl.box.keyPair() +``` + + +### Client ID + +The public key part of the [Client Keypair](#client-keypair) (32 bytes). + +### Session + +A session is defined by a pair of two client IDs. Both the app and the wallet create their own [Client IDs](#client-id). + + +### Creating client Keypair + +``` +(a,A) <- nacl.box.keyPair() +``` + +### Encryption + +All requests from the app (except the initial request) and all responses from the wallet are encrypted. + +Given a binary encoding of message **m**, recipient’s [Client ID](#client-id) **X** and sender’s private key **y** the message is encrypted as follows: + +``` +nonce <- random(24 bytes) +ct <- nacl.box(m, nonce, X, y) +M <- nonce ++ ct +``` + +That is, the final message **M** has the first 24 bytes set to the random nonce. + +### Decryption + +To decrypt the message **M**, the recipient uses its private key **x** and sender’s public key **Y** (aka [Client ID](#client-id)): + +``` +nonce <- M[0..24] +ct <- M[24..] +m <- nacl.box.open(ct, nonce, Y, x) +``` + +Plaintext **m** is recovered and parsed per [Requests/Responses](/develop/dapps/ton-connect/protocol/requests-responses#requests-and-responses). \ No newline at end of file diff --git a/docs/develop/dapps/ton-connect/wallet-guidelines.md b/docs/develop/dapps/ton-connect/protocol/wallet-guidelines.md similarity index 97% rename from docs/develop/dapps/ton-connect/wallet-guidelines.md rename to docs/develop/dapps/ton-connect/protocol/wallet-guidelines.md index a3643efd6f..e98e5b9607 100644 --- a/docs/develop/dapps/ton-connect/wallet-guidelines.md +++ b/docs/develop/dapps/ton-connect/protocol/wallet-guidelines.md @@ -1,4 +1,4 @@ -# TON Wallet Guidelines +# Wallet Guidelines ## Networks @@ -59,7 +59,7 @@ We recommend wallets provide the ability to disconnect session with a specified ## References -* https://github.com/ton-blockchain/ton-connect/blob/main/wallet-guidelines.md +* /develop/dapps/ton-connect/protocol/wallet-guidelines ## See Also diff --git a/docs/develop/dapps/ton-connect/workflow.md b/docs/develop/dapps/ton-connect/protocol/workflow.md similarity index 66% rename from docs/develop/dapps/ton-connect/workflow.md rename to docs/develop/dapps/ton-connect/protocol/workflow.md index 778d371584..08ac003d6b 100644 --- a/docs/develop/dapps/ton-connect/workflow.md +++ b/docs/develop/dapps/ton-connect/protocol/workflow.md @@ -39,15 +39,15 @@ App creates app’s Client Keypair (a, A): (a,A) <- nacl.box.keyPair() ``` -App generates the **InitialRequest**. See [requests spec](https://github.com/ton-blockchain/ton-connect/blob/main/requests-responses.md). +App generates the **InitialRequest**. See [requests spec](/develop/dapps/ton-connect/protocol/requests-responses). -App creates a [universal link](https://github.com/ton-blockchain/ton-connect/blob/main/bridge.md#universal-link) to a target wallet: +App creates a [universal link](/develop/dapps/ton-connect/protocol/bridge#universal-link) to a target wallet: ``` https://?v=2&id=&r= ``` -When using the [JS bridge](https://github.com/ton-blockchain/ton-connect/blob/main/bridge.md#js-bridge), the same request is sent via the `connect()` call: +When using the [JS bridge](/develop/dapps/ton-connect/protocol/bridge#js-bridge), the same request is sent via the `connect()` call: ``` window.[walletJsBridgeKey].tonconnect.connect(2, ) @@ -57,7 +57,7 @@ Parameter **v** specifies the protocol version. Unsupported versions are not acc Parameter **id** specifies app’s Client ID encoded as hex (without '0x' prefix). -Parameter **r** specifies URL-safe json [ConnectRequest](https://github.com/ton-blockchain/ton-connect/blob/main/requests-responses.md#initiating-connection). +Parameter **r** specifies URL-safe json [ConnectRequest](/develop/dapps/ton-connect/protocol/requests-responses#initiating-connection). The link may be embedded in a QR code or clicked directly. @@ -67,11 +67,11 @@ App is not yet in the connected state, and may restart the whole process at any ### Wallet establishes connection -Wallet opens up a link or QR code, reads plaintext app’s **Client ID** (A from parameter “**id”**) and [InitialRequest](https://github.com/ton-blockchain/ton-connect/blob/main/requests-responses.md#initiating-connection) (from parameter **“r”**). +Wallet opens up a link or QR code, reads plaintext app’s **Client ID** (A from parameter “**id”**) and [InitialRequest](/develop/dapps/ton-connect/protocol/requests-responses#initiating-connection) (from parameter **“r”**). -Wallet computes the [InitialResponse](https://github.com/ton-blockchain/ton-connect/blob/main/requests-responses.md#initiating-connection). +Wallet computes the [InitialResponse](/develop/dapps/ton-connect/protocol/requests-responses#initiating-connection). -Wallet generates its own [Client Keypair](https://github.com/ton-blockchain/ton-connect/blob/main/session.md#client-keypair) (b,B) and stores alongside app’s info and ID. +Wallet generates its own [Client Keypair](/develop/dapps/ton-connect/protocol/session#client-keypair) (b,B) and stores alongside app’s info and ID. Wallet encrypts the response and sends it to the **Bridge** using app’s Client ID A. @@ -81,7 +81,7 @@ Wallet connects to the bridge (link to bridge api) and listens for events using App receives the event from the bridge that contains the encrypted message from the Client ID **B.** -App decrypts the message and parses it as [InitialResponse](https://github.com/ton-blockchain/ton-connect/blob/main/requests-responses.md#initiating-connection). +App decrypts the message and parses it as [InitialResponse](/develop/dapps/ton-connect/protocol/requests-responses#initiating-connection). If the reply is valid, App reads wallet info (address, public key, proof of ownership etc.) and remembers the wallet’s ID **B**. @@ -91,11 +91,11 @@ The session is considered established in the app when it has the wallet’s ID a When the user performs an action in the app, it may request confirmation from the wallet. -App generates a [request](https://github.com/ton-blockchain/ton-connect/blob/main/requests-responses.md#messages). +App generates a [request](/develop/dapps/ton-connect/protocol/requests-responses#messages). App encrypts it to the wallet’s key B (see below). -App sends the encrypted message to B over the [Bridge](https://github.com/ton-blockchain/ton-connect/blob/main/bridge.md). +App sends the encrypted message to B over the [Bridge](/develop/dapps/ton-connect/protocol/bridge). App shows “pending confirmation” UI to let user know to open the wallet. @@ -103,13 +103,13 @@ Wallet receives the encrypted message through the Bridge. Wallet decrypts the message and is now assured that it came from the app with ID **A.** -Wallet shows the confirmation dialog to the user, signs transaction and [replies](https://github.com/ton-blockchain/ton-connect/blob/main/requests-responses.md#messages) over the bridge with user’s decision: “Ok, sent” or “User cancelled”. +Wallet shows the confirmation dialog to the user, signs transaction and [replies](/develop/dapps/ton-connect/protocol/requests-responses#messages) over the bridge with user’s decision: “Ok, sent” or “User cancelled”. App receives the encrypted message, decrypts it and closes the “pending confirmation” UI. ## References -* https://github.com/ton-blockchain/ton-connect/blob/main/workflows.md +* /develop/dapps/ton-connect/protocol/workflows ## See Also diff --git a/sidebars.js b/sidebars.js index de446ecae1..c07a3ca8fb 100644 --- a/sidebars.js +++ b/sidebars.js @@ -372,7 +372,6 @@ const sidebars = { items: [ 'develop/dapps/ton-connect/developers', 'develop/dapps/ton-connect/transactions', - 'develop/dapps/ton-connect/workflow', ], }, { @@ -380,16 +379,20 @@ const sidebars = { label: 'Protocol specifications', items: [ { - type: 'link', + type: 'doc', label: 'TON Connect Protocol', - href: 'https://github.com/ton-blockchain/ton-connect', + id: 'develop/dapps/ton-connect/protocol/README', }, + 'develop/dapps/ton-connect/protocol/workflow', + 'develop/dapps/ton-connect/protocol/bridge', + 'develop/dapps/ton-connect/protocol/session', + 'develop/dapps/ton-connect/protocol/requests-responses', + 'develop/dapps/ton-connect/protocol/wallet-guidelines', { type: 'link', - label: 'TON Connect Wallets', + label: 'Wallets List', href: 'https://github.com/ton-blockchain/wallets-list', }, - 'develop/dapps/ton-connect/wallet-guidelines', ], }, {