Skip to content

feat: add sign data support #276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
export interface SignDataRpcRequest {
method: 'signData';
params: [
{
schema_crc: number;
cell: string;
}
];
params: [string];
id: string;
}
3 changes: 2 additions & 1 deletion packages/protocol/src/models/feature.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export type Feature = SendTransactionFeatureDeprecated | SendTransactionFeature | SignDataFeature;
export type Feature = SendTransactionFeatureDeprecated | SendTransactionFeature | SignDataFeature | SignDataFeatureDeprecated;

export type SendTransactionFeatureDeprecated = 'SendTransaction';
export type SendTransactionFeature = { name: 'SendTransaction'; maxMessages: number };
export type SignDataFeatureDeprecated = 'SignData';
export type SignDataFeature = { name: 'SignData' };
3 changes: 2 additions & 1 deletion packages/protocol/src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export {
Feature,
SendTransactionFeatureDeprecated,
SendTransactionFeature,
SignDataFeature
SignDataFeature,
SignDataFeatureDeprecated
} from './feature';
export { CHAIN } from './CHAIN';
14 changes: 12 additions & 2 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export {
createTransactionSentForSignatureEvent,
createTransactionSigningFailedEvent,
createTransactionSignedEvent,
createDataSentForSignatureEvent,
createDataSigningFailedEvent,
createDataSignedEvent,
createRequestVersionEvent,
createResponseVersionEvent,
createVersionInfo
Expand All @@ -43,6 +46,11 @@ export type {
TransactionSignedEvent,
TransactionSentForSignatureEvent,
TransactionSigningFailedEvent,
SignDataInfo,
DataSigningEvent,
DataSignedEvent,
DataSentForSignatureEvent,
DataSigningFailedEvent,
SdkActionEvent,
RequestVersionEvent,
ResponseVersionEvent,
Expand All @@ -57,15 +65,17 @@ export {
DeviceInfo,
Feature,
SendTransactionFeature,
SignDataFeature,
SendTransactionFeatureDeprecated,
SignDataFeature,
SignDataFeatureDeprecated,
TonProofItemReply,
TonProofItemReplySuccess,
TonProofItemReplyError,
ConnectItemReplyError,
CONNECT_ITEM_ERROR_CODES,
CONNECT_EVENT_ERROR_CODES,
SEND_TRANSACTION_ERROR_CODES
SEND_TRANSACTION_ERROR_CODES,
SIGN_DATA_ERROR_CODES
} from '@tonconnect/protocol';
export { toUserFriendlyAddress } from './utils/address';
export { isTelegramUrl, encodeTelegramUrlParameters } from './utils/url';
1 change: 1 addition & 0 deletions packages/sdk/src/models/methods/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './connect';
export * from './send-transaction';
export * from './sign-data';
2 changes: 2 additions & 0 deletions packages/sdk/src/models/methods/sign-data/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { SignDataRequest } from './sign-data-request';
export { SignDataResponse } from './sign-data-response';
16 changes: 16 additions & 0 deletions packages/sdk/src/models/methods/sign-data/sign-data-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

export interface SignDataRequest {
/**
* (integer): indicates the layout of payload cell that in turn defines domain separation.
*/
schema_crc: number;
/**
* (string, base64 encoded Cell): contains arbitrary data per its TL-B definition.
*/
cell: string;

/**
* (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.
*/
publicKey?: string;
}
11 changes: 11 additions & 0 deletions packages/sdk/src/models/methods/sign-data/sign-data-response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface SignDataResponse {
/**
* base64 encoded signature
*/
signature: string;

/**
* UNIX timestamp in seconds (UTC) at the moment on creating the signature.
*/
timestamp: string;
}
51 changes: 51 additions & 0 deletions packages/sdk/src/parsers/sign-data-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
CONNECT_EVENT_ERROR_CODES,
SIGN_DATA_ERROR_CODES,
SignDataRpcRequest,
SignDataRpcResponseError,
SignDataRpcResponseSuccess
} from '@tonconnect/protocol';
import { BadRequestError, TonConnectError, UnknownAppError, UserRejectsError } from 'src/errors';
import { UnknownError } from 'src/errors/unknown.error';
import { SignDataRequest, SignDataResponse } from 'src/models/methods';
import { RpcParser } from 'src/parsers/rpc-parser';
import { WithoutId } from 'src/utils/types';

const signDataErrors: Partial<Record<CONNECT_EVENT_ERROR_CODES, typeof TonConnectError>> = {
[SIGN_DATA_ERROR_CODES.UNKNOWN_ERROR]: UnknownError,
[SIGN_DATA_ERROR_CODES.USER_REJECTS_ERROR]: UserRejectsError,
[SIGN_DATA_ERROR_CODES.BAD_REQUEST_ERROR]: BadRequestError,
[SIGN_DATA_ERROR_CODES.UNKNOWN_APP_ERROR]: UnknownAppError
};

class SignDataParser extends RpcParser<'signData'> {
convertToRpcRequest(
request: SignDataRequest
): WithoutId<SignDataRpcRequest> {
return {
method: 'signData',
params: [JSON.stringify(request)]
};
}

parseAndThrowError(response: WithoutId<SignDataRpcResponseError>): never {
let ErrorConstructor: typeof TonConnectError = UnknownError;

if (response.error.code in signDataErrors) {
ErrorConstructor = signDataErrors[response.error.code] || UnknownError;
}

throw new ErrorConstructor(response.error.message);
}

convertFromRpcResponse(
rpcResponse: WithoutId<SignDataRpcResponseSuccess>
): SignDataResponse {
return {
signature: rpcResponse.result.signature,
timestamp: rpcResponse.result.timestamp
};
}
}

export const signDataParser = new SignDataParser();
17 changes: 16 additions & 1 deletion packages/sdk/src/ton-connect.interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TonConnectError } from 'src/errors';
import { Account, Wallet, WalletConnectionSource, WalletConnectionSourceHTTP } from 'src/models';
import { SendTransactionRequest, SendTransactionResponse } from 'src/models/methods';
import { SendTransactionRequest, SendTransactionResponse, SignDataRequest, SignDataResponse } from 'src/models/methods';
import { ConnectAdditionalRequest } from 'src/models/methods/connect/connect-additional-request';
import { WalletInfo } from 'src/models/wallet/wallet-info';
import { WalletConnectionSourceJS } from 'src/models/wallet/wallet-connection-source';
Expand Down Expand Up @@ -92,4 +92,19 @@ export interface ITonConnect {
transaction: SendTransactionRequest,
onRequestSent?: () => void
): Promise<SendTransactionResponse>;

/**
* Asks connected wallet to sign data
* @param data data to sign.
* @param options (optional) onRequestSent callback will be called after the data is signed and signal to abort the request.
* @returns signed data
* If user rejects sign, method will throw the corresponding error.
*/
signData(
data: SignDataRequest,
options?: {
onRequestSent?: () => void,
signal?: AbortSignal
}
): Promise<SignDataResponse>;
}
85 changes: 76 additions & 9 deletions packages/sdk/src/ton-connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ConnectItem,
ConnectRequest,
SendTransactionRpcResponseSuccess,
SignDataRpcResponseSuccess,
TonAddressItemReply,
TonProofItemReply,
WalletEvent
Expand All @@ -21,7 +22,7 @@ import {
WalletConnectionSourceHTTP,
WalletInfo
} from 'src/models';
import { SendTransactionRequest, SendTransactionResponse } from 'src/models/methods';
import { SendTransactionRequest, SendTransactionResponse, SignDataRequest, SignDataResponse } from 'src/models/methods';
import { ConnectAdditionalRequest } from 'src/models/methods/connect/connect-additional-request';
import { TonConnectOptions } from 'src/models/ton-connect-options';
import {
Expand All @@ -30,6 +31,7 @@ import {
} from 'src/models/wallet/wallet-connection-source';
import { connectErrorsParser } from 'src/parsers/connect-errors-parser';
import { sendTransactionParser } from 'src/parsers/send-transaction-parser';
import { signDataParser } from 'src/parsers/sign-data-parser';
import { BridgeProvider } from 'src/provider/bridge/bridge-provider';
import { InjectedProvider } from 'src/provider/injected/injected-provider';
import { Provider } from 'src/provider/provider';
Expand All @@ -39,7 +41,7 @@ import { ITonConnect } from 'src/ton-connect.interface';
import { getDocument, getWebPageManifest } from 'src/utils/web-api';
import { WalletsListManager } from 'src/wallets-list-manager';
import { WithoutIdDistributive } from 'src/utils/types';
import { checkSendTransactionSupport } from 'src/utils/feature-support';
import { checkSendTransactionSupport, checkSignDataSupport } from 'src/utils/feature-support';
import { callForSuccess } from 'src/utils/call-for-success';
import { logDebug, logError } from 'src/utils/log';
import { createAbortController } from 'src/utils/create-abort-controller';
Expand Down Expand Up @@ -215,10 +217,10 @@ export class TonConnect implements ITonConnect {
requestOrOptions?:
| ConnectAdditionalRequest
| {
request?: ConnectAdditionalRequest;
openingDeadlineMS?: number;
signal?: AbortSignal;
}
request?: ConnectAdditionalRequest;
openingDeadlineMS?: number;
signal?: AbortSignal;
}
): void | string {
// TODO: remove deprecated method
const options: {
Expand Down Expand Up @@ -393,9 +395,9 @@ export class TonConnect implements ITonConnect {
transaction: SendTransactionRequest,
optionsOrOnRequestSent?:
| {
onRequestSent?: () => void;
signal?: AbortSignal;
}
onRequestSent?: () => void;
signal?: AbortSignal;
}
| (() => void)
): Promise<SendTransactionResponse> {
// TODO: remove deprecated method
Expand Down Expand Up @@ -623,4 +625,69 @@ export class TonConnect implements ITonConnect {
items
};
}

public signData(
data: SignDataRequest,
options?: {
onRequestSent?: () => void;
signal?: AbortSignal;
}
): Promise<SignDataResponse>;

public async signData(
data: SignDataRequest,
optionsOrOnRequestSent?:
| {
onRequestSent?: () => void;
signal?: AbortSignal;
}
| (() => void)
): Promise<SignDataResponse> {
// TODO: remove deprecated method
const options: {
onRequestSent?: () => void;
signal?: AbortSignal;
} = {};
if (typeof optionsOrOnRequestSent === 'function') {
options.onRequestSent = optionsOrOnRequestSent;
} else {
options.onRequestSent = optionsOrOnRequestSent?.onRequestSent;
options.signal = optionsOrOnRequestSent?.signal;
}

const abortController = createAbortController(options?.signal);
if (abortController.signal.aborted) {
throw new TonConnectError('data sending was aborted');
}

this.checkConnection();
checkSignDataSupport(this.wallet!.device.features);

this.tracker.trackDataSentForSignature(this.wallet, data);


const response = await this.provider!.sendRequest(
signDataParser.convertToRpcRequest({
...data,
}),
{ onRequestSent: options.onRequestSent, signal: abortController.signal }
);

if (signDataParser.isError(response)) {
this.tracker.trackDataSigningFailed(
this.wallet,
data,
response.error.message,
response.error.code
);
return signDataParser.parseAndThrowError(response);
}

const result = signDataParser.convertFromRpcResponse(
response as SignDataRpcResponseSuccess
);
this.tracker.trackDataSigned(this.wallet, data, result);
return result;
}

}
43 changes: 43 additions & 0 deletions packages/sdk/src/tracker/ton-connect-tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {
createConnectionRestoringErrorEvent,
createConnectionRestoringStartedEvent,
createConnectionStartedEvent,
createDataSentForSignatureEvent,
createDataSignedEvent,
createDataSigningFailedEvent,
createDisconnectionEvent,
createRequestVersionEvent,
createResponseVersionEvent,
Expand Down Expand Up @@ -289,4 +292,44 @@ export class TonConnectTracker {
this.dispatchUserActionEvent(event);
} catch (e) {}
}

/**
* Track sign data init event.
* @param args
*/
public trackDataSentForSignature(
...args: WithoutVersion<Parameters<typeof createDataSentForSignatureEvent>>
): void {
try {
const event = createDataSentForSignatureEvent(this.version, ...args);
this.dispatchUserActionEvent(event);
} catch (e) {}
}

/**
* Track sign data success event.
* @param args
*/
public trackDataSigned(
...args: WithoutVersion<Parameters<typeof createDataSignedEvent>>
): void {
try {
const event = createDataSignedEvent(this.version, ...args);
this.dispatchUserActionEvent(event);
} catch (e) {}
}

/**
* Track sign data error event.
* @param args
*/
public trackDataSigningFailed(
...args: WithoutVersion<Parameters<typeof createDataSigningFailedEvent>>
): void {
try {
const event = createDataSigningFailedEvent(this.version, ...args);
this.dispatchUserActionEvent(event);
} catch (e) {}
}

}
Loading