From 4005e7a1f696106925f5cb380c29a007a4ca418e Mon Sep 17 00:00:00 2001 From: krigga Date: Sat, 24 Feb 2024 00:01:32 +0300 Subject: [PATCH 1/6] feat: 2.1.0 features --- package.json | 2 +- source/TonTransport.ts | 484 ++++++++++++++++++++++++++++------- source/utils/ledgerWriter.ts | 16 +- 3 files changed, 414 insertions(+), 88 deletions(-) diff --git a/package.json b/package.json index 7920697..da1897f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ton-community/ton-ledger", - "version": "7.0.1", + "version": "7.1.0-pre.0", "repository": "https://github.com/ton-community/ton-ledger-ts", "author": "Steve Korshakov ", "license": "MIT", diff --git a/source/TonTransport.ts b/source/TonTransport.ts index 6b9be76..38168a5 100644 --- a/source/TonTransport.ts +++ b/source/TonTransport.ts @@ -1,8 +1,8 @@ import Transport from "@ledgerhq/hw-transport"; import { Address, beginCell, Cell, contractAddress, SendMode, StateInit, storeStateInit } from "@ton/core"; -import { signVerify } from '@ton/crypto'; +import { sha256_sync, signVerify } from '@ton/crypto'; import { AsyncLock } from 'teslabot'; -import { writeAddress, writeCellRef, writeUint16, writeUint32, writeUint64, writeUint8, writeVarUInt } from "./utils/ledgerWriter"; +import { writeAddress, writeCellInline, writeCellRef, writeUint16, writeUint32, writeUint48, writeUint64, writeUint8, writeVarUInt } from "./utils/ledgerWriter"; import { getInit } from "./utils/getInit"; const LEDGER_SYSTEM = 0xB0; @@ -13,10 +13,21 @@ const INS_SIGN_TX = 0x06; const INS_PROOF = 0x08; const INS_SIGN_DATA = 0x09; +const DEFAULT_SUBWALLET_ID = 698983191; + export type TonPayloadFormat = + | { type: 'unsafe', message: Cell } | { type: 'comment', text: string } | { type: 'jetton-transfer', queryId: bigint | null, amount: bigint, destination: Address, responseDestination: Address, customPayload: Cell | null, forwardAmount: bigint, forwardPayload: Cell | null } | { type: 'nft-transfer', queryId: bigint | null, newOwner: Address, responseDestination: Address, customPayload: Cell | null, forwardAmount: bigint, forwardPayload: Cell | null } + | { type: 'jetton-burn', queryId: bigint | null, amount: bigint, responseDestination: Address, customPayload: Cell | Buffer | null } + | { type: 'add-whitelist', queryId: bigint | null, address: Address } + | { type: 'single-nominator-withdraw', queryId: bigint | null, amount: bigint } + | { type: 'single-nominator-change-validator', queryId: bigint | null, address: Address } + | { type: 'tonstakers-deposit', queryId: bigint | null, appId: bigint | null } + | { type: 'vote-for-proposal', queryId: bigint | null, votingAddress: Address, expirationDate: number, vote: boolean, needConfirmation: boolean } + | { type: 'change-dns-record', queryId: bigint | null, record: { type: 'wallet', value: { address: Address, capabilities: { isWallet: boolean } | null } | null } | { type: 'unknown', key: Buffer, value: Cell | null } } + | { type: 'token-bridge-pay-swap', queryId: bigint | null, swapId: Buffer } export type SignDataRequest = | { type: 'plaintext', text: string } @@ -47,6 +58,363 @@ function processAddressFlags(opts?: { testOnly?: boolean, bounceable?: boolean, return { bounceable, testOnly, chain, flags }; } +function convertPayload(input: TonPayloadFormat | undefined): { payload: Cell | null, hints: Buffer } { + let payload: Cell | null = null; + let hints: Buffer = Buffer.concat([writeUint8(0)]); + + if (input === undefined) { + return { + payload, + hints, + }; + } + + switch (input.type) { + case 'unsafe': { + payload = input.message; + break; + } + case 'comment': { + hints = Buffer.concat([ + writeUint8(1), + writeUint32(0x00), + writeUint16(Buffer.from(input.text).length), + Buffer.from(input.text) + ]); + payload = beginCell() + .storeUint(0, 32) + .storeBuffer(Buffer.from(input.text)) + .endCell(); + break; + } + case 'jetton-transfer': + case 'nft-transfer': { + hints = Buffer.concat([ + writeUint8(1), + writeUint32(input.type === 'jetton-transfer' ? 0x01 : 0x02) + ]); + + let b = beginCell() + .storeUint(input.type === 'jetton-transfer' ? 0x0f8a7ea5 : 0x5fcc3d14, 32); + let d = Buffer.alloc(0); + + if (input.queryId !== null) { + d = Buffer.concat([d, writeUint8(1), writeUint64(input.queryId)]); + b = b.storeUint(input.queryId, 64); + } else { + d = Buffer.concat([d, writeUint8(0)]); + b = b.storeUint(0, 64); + } + + if (input.type === 'jetton-transfer') { + d = Buffer.concat([d, writeVarUInt(input.amount)]); + b = b.storeCoins(input.amount); + + d = Buffer.concat([d, writeAddress(input.destination)]); + b = b.storeAddress(input.destination); + } else { + d = Buffer.concat([d, writeAddress(input.newOwner)]); + b = b.storeAddress(input.newOwner); + } + + d = Buffer.concat([d, writeAddress(input.responseDestination)]); + b = b.storeAddress(input.responseDestination); + + if (input.customPayload !== null) { + d = Buffer.concat([d, writeUint8(1), writeCellRef(input.customPayload)]); + b = b.storeMaybeRef(input.customPayload); + } else { + d = Buffer.concat([d, writeUint8(0)]); + b = b.storeMaybeRef(input.customPayload); + } + + d = Buffer.concat([d, writeVarUInt(input.forwardAmount)]); + b = b.storeCoins(input.forwardAmount); + + if (input.forwardPayload !== null) { + d = Buffer.concat([d, writeUint8(1), writeCellRef(input.forwardPayload)]); + b = b.storeMaybeRef(input.forwardPayload); + } else { + d = Buffer.concat([d, writeUint8(0)]); + b = b.storeMaybeRef(input.forwardPayload); + } + + payload = b.endCell(); + hints = Buffer.concat([ + hints, + writeUint16(d.length), + d + ]); + break; + } + case 'jetton-burn': { + hints = Buffer.concat([ + writeUint8(1), + writeUint32(0x03) + ]); + + let b = beginCell() + .storeUint(0x595f07bc, 32); + let d = Buffer.alloc(0); + + if (input.queryId !== null) { + d = Buffer.concat([d, writeUint8(1), writeUint64(input.queryId)]); + b = b.storeUint(input.queryId, 64); + } else { + d = Buffer.concat([d, writeUint8(0)]); + b = b.storeUint(0, 64); + } + + d = Buffer.concat([d, writeVarUInt(input.amount)]); + b = b.storeCoins(input.amount); + + d = Buffer.concat([d, writeAddress(input.responseDestination)]); + b = b.storeAddress(input.responseDestination); + + if (input.customPayload === null) { + d = Buffer.concat([d, writeUint8(0)]); + b = b.storeMaybeRef(input.customPayload); + } else if (input.customPayload instanceof Cell) { + d = Buffer.concat([d, writeUint8(1), writeCellRef(input.customPayload)]); + b = b.storeMaybeRef(input.customPayload); + } else { + d = Buffer.concat([d, writeUint8(2), writeCellInline(input.customPayload)]); + b = b.storeMaybeRef(beginCell().storeBuffer(input.customPayload).endCell()); + } + + payload = b.endCell(); + hints = Buffer.concat([ + hints, + writeUint16(d.length), + d + ]); + break; + } + case 'add-whitelist': + case 'single-nominator-change-validator': { + hints = Buffer.concat([ + writeUint8(1), + writeUint32(input.type === 'add-whitelist' ? 0x04 : 0x06) + ]); + + let b = beginCell() + .storeUint(input.type === 'add-whitelist' ? 0x7258a69b : 0x1001, 32); + let d = Buffer.alloc(0); + + if (input.queryId !== null) { + d = Buffer.concat([d, writeUint8(1), writeUint64(input.queryId)]); + b = b.storeUint(input.queryId, 64); + } else { + d = Buffer.concat([d, writeUint8(0)]); + b = b.storeUint(0, 64); + } + + d = Buffer.concat([d, writeAddress(input.address)]); + b = b.storeAddress(input.address); + + payload = b.endCell(); + hints = Buffer.concat([ + hints, + writeUint16(d.length), + d + ]); + break; + } + case 'single-nominator-withdraw': { + hints = Buffer.concat([ + writeUint8(1), + writeUint32(0x05) + ]); + + let b = beginCell() + .storeUint(0x1000, 32); + let d = Buffer.alloc(0); + + if (input.queryId !== null) { + d = Buffer.concat([d, writeUint8(1), writeUint64(input.queryId)]); + b = b.storeUint(input.queryId, 64); + } else { + d = Buffer.concat([d, writeUint8(0)]); + b = b.storeUint(0, 64); + } + + d = Buffer.concat([d, writeVarUInt(input.amount)]); + b = b.storeCoins(input.amount); + + payload = b.endCell(); + hints = Buffer.concat([ + hints, + writeUint16(d.length), + d + ]); + break; + } + case 'tonstakers-deposit': { + hints = Buffer.concat([ + writeUint8(1), + writeUint32(0x07) + ]); + + let b = beginCell() + .storeUint(0x47d54391, 32); + let d = Buffer.alloc(0); + + if (input.queryId !== null) { + d = Buffer.concat([d, writeUint8(1), writeUint64(input.queryId)]); + b = b.storeUint(input.queryId, 64); + } else { + d = Buffer.concat([d, writeUint8(0)]); + b = b.storeUint(0, 64); + } + + if (input.appId !== null) { + d = Buffer.concat([d, writeUint8(1), writeUint64(input.appId)]); + b = b.storeUint(input.appId, 64); + } else { + d = Buffer.concat([d, writeUint8(0)]); + } + + payload = b.endCell(); + hints = Buffer.concat([ + hints, + writeUint16(d.length), + d + ]); + break; + } + case 'vote-for-proposal': { + hints = Buffer.concat([ + writeUint8(1), + writeUint32(0x08) + ]); + + let b = beginCell() + .storeUint(0x69fb306c, 32); + let d = Buffer.alloc(0); + + if (input.queryId !== null) { + d = Buffer.concat([d, writeUint8(1), writeUint64(input.queryId)]); + b = b.storeUint(input.queryId, 64); + } else { + d = Buffer.concat([d, writeUint8(0)]); + b = b.storeUint(0, 64); + } + + d = Buffer.concat([d, writeAddress(input.votingAddress)]); + b = b.storeAddress(input.votingAddress); + + d = Buffer.concat([d, writeUint48(input.expirationDate)]); + b = b.storeUint(input.expirationDate, 48); + + d = Buffer.concat([d, writeUint8(input.vote ? 1 : 0), writeUint8(input.needConfirmation ? 1 : 0)]); + b = b.storeBit(input.vote).storeBit(input.needConfirmation); + + payload = b.endCell(); + hints = Buffer.concat([ + hints, + writeUint16(d.length), + d + ]); + break; + } + case 'change-dns-record': { + hints = Buffer.concat([ + writeUint8(1), + writeUint32(0x09) + ]); + + let b = beginCell() + .storeUint(0x4eb1f0f9, 32); + let d = Buffer.alloc(0); + + if (input.queryId !== null) { + d = Buffer.concat([d, writeUint8(1), writeUint64(input.queryId)]); + b = b.storeUint(input.queryId, 64); + } else { + d = Buffer.concat([d, writeUint8(0)]); + b = b.storeUint(0, 64); + } + + if (input.record.type === 'unknown' && input.record.key.length !== 32) { + throw new Error('DNS record key length must be 32 bytes long'); + } + b = b.storeBuffer(input.record.type === 'wallet' ? sha256_sync('wallet') : input.record.key); + + d = Buffer.concat([d, writeUint8(input.record.value === null ? 0 : 1), writeUint8(input.record.type === 'wallet' ? 0 : 1)]); + + if (input.record.type === 'wallet') { + if (input.record.value !== null) { + d = Buffer.concat([d, writeAddress(input.record.value.address), writeUint8(input.record.value.capabilities === null ? 0 : 1)]); + let rb = beginCell().storeUint(0x9fd3, 16).storeAddress(input.record.value.address).storeUint(input.record.value.capabilities === null ? 0 : 1, 8); + if (input.record.value.capabilities !== null) { + d = Buffer.concat([d, writeUint8(input.record.value.capabilities.isWallet ? 1 : 0)]); + if (input.record.value.capabilities.isWallet) { + rb = rb.storeBit(true).storeUint(0x2177, 16); + } + rb = rb.storeBit(false); + } + b = b.storeRef(rb); + } + } else { + d = Buffer.concat([d, input.record.key]); + if (input.record.value !== null) { + d = Buffer.concat([d, writeCellRef(input.record.value)]); + b = b.storeRef(input.record.value); + } + } + + payload = b.endCell(); + hints = Buffer.concat([ + hints, + writeUint16(d.length), + d + ]); + break; + } + case 'token-bridge-pay-swap': { + hints = Buffer.concat([ + writeUint8(1), + writeUint32(0x0A) + ]); + + let b = beginCell() + .storeUint(8, 32); + let d = Buffer.alloc(0); + + if (input.queryId !== null) { + d = Buffer.concat([d, writeUint8(1), writeUint64(input.queryId)]); + b = b.storeUint(input.queryId, 64); + } else { + d = Buffer.concat([d, writeUint8(0)]); + b = b.storeUint(0, 64); + } + + if (input.swapId.length !== 32) { + throw new Error('Token bridge swap ID must be 32 bytes long'); + } + + d = Buffer.concat([d, input.swapId]); + b = b.storeBuffer(input.swapId); + + payload = b.endCell(); + hints = Buffer.concat([ + hints, + writeUint16(d.length), + d + ]); + break; + } + default: { + throw new Error('Unknown payload type: ' + (input as any).type); + } + } + + return { + payload, + hints, + }; +} + export class TonTransport { readonly transport: Transport; #lock = new AsyncLock(); @@ -280,7 +648,11 @@ export class TonTransport { bounce: boolean, amount: bigint, stateInit?: StateInit, - payload?: TonPayloadFormat + payload?: TonPayloadFormat, + walletSpecifiers?: { + subwalletId?: number, + includeWalletOp: boolean, + }, } ) => { @@ -298,7 +670,19 @@ export class TonTransport { // let pkg = Buffer.concat([ - writeUint8(0), // Header + writeUint8(transaction.walletSpecifiers === undefined ? 0 : 1), // tag + ]); + + if (transaction.walletSpecifiers !== undefined) { + pkg = Buffer.concat([ + pkg, + writeUint32(transaction.walletSpecifiers.subwalletId ?? DEFAULT_SUBWALLET_ID), + writeUint8(transaction.walletSpecifiers.includeWalletOp ? 1 : 0), + ]); + } + + pkg = Buffer.concat([ + pkg, writeUint32(transaction.seqno), writeUint32(transaction.timeout), writeVarUInt(transaction.amount), @@ -333,83 +717,7 @@ export class TonTransport { // Payload // - let payload: Cell | null = null; - let hints: Buffer = Buffer.concat([writeUint8(0)]); - if (transaction.payload) { - if (transaction.payload.type === 'comment') { - hints = Buffer.concat([ - writeUint8(1), - writeUint32(0x00), - writeUint16(Buffer.from(transaction.payload.text).length), - Buffer.from(transaction.payload.text) - ]); - payload = beginCell() - .storeUint(0, 32) - .storeBuffer(Buffer.from(transaction.payload.text)) - .endCell() - } else if (transaction.payload.type === 'jetton-transfer' || transaction.payload.type === 'nft-transfer') { - hints = Buffer.concat([ - writeUint8(1), - writeUint32(transaction.payload.type === 'jetton-transfer' ? 0x01 : 0x02) - ]); - - let b = beginCell() - .storeUint(transaction.payload.type === 'jetton-transfer' ? 0x0f8a7ea5 : 0x5fcc3d14, 32); - let d = Buffer.alloc(0); - - if (transaction.payload.queryId !== null) { - d = Buffer.concat([d, writeUint8(1), writeUint64(transaction.payload.queryId)]); - b = b.storeUint(transaction.payload.queryId, 64); - } else { - d = Buffer.concat([d, writeUint8(0)]); - b = b.storeUint(0, 64); - } - - if (transaction.payload.type === 'jetton-transfer') { - d = Buffer.concat([d, writeVarUInt(transaction.payload.amount)]); - b = b.storeCoins(transaction.payload.amount); - - d = Buffer.concat([d, writeAddress(transaction.payload.destination)]); - b = b.storeAddress(transaction.payload.destination); - } else { - d = Buffer.concat([d, writeAddress(transaction.payload.newOwner)]); - b = b.storeAddress(transaction.payload.newOwner); - } - - d = Buffer.concat([d, writeAddress(transaction.payload.responseDestination)]); - b = b.storeAddress(transaction.payload.responseDestination); - - if (transaction.payload.customPayload !== null) { - d = Buffer.concat([d, writeUint8(1), writeCellRef(transaction.payload.customPayload)]); - b = b.storeMaybeRef(transaction.payload.customPayload); - } else { - d = Buffer.concat([d, writeUint8(0)]); - b = b.storeMaybeRef(transaction.payload.customPayload); - } - - d = Buffer.concat([d, writeVarUInt(transaction.payload.forwardAmount)]); - b = b.storeCoins(transaction.payload.forwardAmount); - - if (transaction.payload.forwardPayload !== null) { - d = Buffer.concat([d, writeUint8(1), writeCellRef(transaction.payload.forwardPayload)]); - b = b.storeMaybeRef(transaction.payload.forwardPayload); - } else { - d = Buffer.concat([d, writeUint8(0)]); - b = b.storeMaybeRef(transaction.payload.forwardPayload); - } - - payload = b.endCell(); - hints = Buffer.concat([ - hints, - writeUint16(d.length), - d - ]) - } - } - - // - // Serialize payload - // + const { payload, hints } = convertPayload(transaction.payload); if (payload) { pkg = Buffer.concat([ @@ -478,12 +786,16 @@ export class TonTransport { } // Transfer message - let transfer = beginCell() - .storeUint(698983191, 32) + let transferB = beginCell() + .storeUint(transaction.walletSpecifiers?.subwalletId ?? DEFAULT_SUBWALLET_ID, 32) .storeUint(transaction.timeout, 32) - .storeUint(transaction.seqno, 32) - .storeUint(0, 8) - .storeUint(transaction.sendMode, 8) + .storeUint(transaction.seqno, 32); + + if (transaction.walletSpecifiers?.includeWalletOp ?? true) { + transferB = transferB.storeUint(0, 8) + } + + let transfer = transferB.storeUint(transaction.sendMode, 8) .storeRef(orderBuilder.endCell()) .endCell(); diff --git a/source/utils/ledgerWriter.ts b/source/utils/ledgerWriter.ts index 94c3dd4..e329e7c 100644 --- a/source/utils/ledgerWriter.ts +++ b/source/utils/ledgerWriter.ts @@ -12,6 +12,13 @@ export function writeUint16(value: number) { return b; } +export function writeUint48(value: number) { + let b = Buffer.alloc(6); + b.writeUint16BE(value >> 32, 0); + b.writeUint32BE(value & ((1 << 32) - 1), 2); + return b; +} + export function writeUint64(value: bigint) { return beginCell().storeUint(value, 64).endCell().beginParse().loadBuffer(8); } @@ -39,4 +46,11 @@ export function writeCellRef(ref: Cell) { writeUint16(ref.depth()), ref.hash() ]) -} \ No newline at end of file +} + +export function writeCellInline(bytes: Buffer) { + return Buffer.concat([ + writeUint8(bytes.length), + bytes, + ]); +} From f27bfb44ed729b5b168573d1f84f28bf109bc4cb Mon Sep 17 00:00:00 2001 From: krigga Date: Thu, 14 Mar 2024 20:55:12 +0300 Subject: [PATCH 2/6] feat: parseMessage helper feat: get settings command --- package.json | 2 +- source/TonTransport.ts | 334 +++++++++++++++++++++++++++++++++++++++++ source/index.ts | 2 +- 3 files changed, 336 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index da1897f..a23a972 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ton-community/ton-ledger", - "version": "7.1.0-pre.0", + "version": "7.1.0-pre.1", "repository": "https://github.com/ton-community/ton-ledger-ts", "author": "Steve Korshakov ", "license": "MIT", diff --git a/source/TonTransport.ts b/source/TonTransport.ts index 38168a5..71f54b4 100644 --- a/source/TonTransport.ts +++ b/source/TonTransport.ts @@ -12,6 +12,7 @@ const INS_ADDRESS = 0x05; const INS_SIGN_TX = 0x06; const INS_PROOF = 0x08; const INS_SIGN_DATA = 0x09; +const INS_SETTINGS = 0x0A; const DEFAULT_SUBWALLET_ID = 698983191; @@ -29,6 +30,328 @@ export type TonPayloadFormat = | { type: 'change-dns-record', queryId: bigint | null, record: { type: 'wallet', value: { address: Address, capabilities: { isWallet: boolean } | null } | null } | { type: 'unknown', key: Buffer, value: Cell | null } } | { type: 'token-bridge-pay-swap', queryId: bigint | null, swapId: Buffer } +const dnsWalletKey = Buffer.from([0xe8, 0xd4, 0x40, 0x50, 0x87, 0x3d, 0xba, 0x86, 0x5a, 0xa7, 0xc1, 0x70, 0xab, 0x4c, 0xce, 0x64, + 0xd9, 0x08, 0x39, 0xa3, 0x4d, 0xcf, 0xd6, 0xcf, 0x71, 0xd1, 0x4e, 0x02, 0x05, 0x44, 0x3b, 0x1b]); + +function normalizeQueryId(qid: bigint): bigint | null { + return qid === 0n ? null : qid; +} + +export function parseMessage(cell: Cell, opts?: { disallowUnsafe?: boolean, disallowModification?: boolean, encodeJettonBurnEthAddressAsHex?: boolean }): TonPayloadFormat | undefined { + const params = { + disallowUnsafe: false, + disallowModification: false, + encodeJettonBurnEthAddressAsHex: true, + ...opts, + }; + + if (cell.hash().equals(new Cell().hash())) { + return undefined; + } + + let s = cell.beginParse(); + try { + const op = s.loadUint(32); + switch (op) { + case 0: { + const str = s.loadStringTail(); + s.endParse(); + + if (str.length > 120) { + throw new Error('Comment must be at most 120 ASCII characters long'); + } + + for (const c of str) { + if (c.charCodeAt(0) < 0x20 || c.charCodeAt(0) >= 0x7f) { + throw new Error('Comment must only contain printable ASCII characters'); + } + } + + return { + type: 'comment', + text: str, + }; + } + case 0x0f8a7ea5: { + const queryId = normalizeQueryId(s.loadUintBig(64)); + const amount = s.loadCoins(); + const destination = s.loadAddress(); + const responseDestination = s.loadAddress(); + const customPayload = s.loadMaybeRef(); + const forwardAmount = s.loadCoins(); + + let forwardPayload: Cell | null = null; + if (s.loadBit()) { + forwardPayload = s.loadRef(); + } else { + const p = s.asCell(); + s = new Cell().beginParse(); // clear the slice + if (!p.hash().equals(new Cell().hash())) { + if (params.disallowModification) { + throw new Error('Jetton transfer message would be modified'); + } + forwardPayload = p; + } + } + + s.endParse(); + + return { + type: 'jetton-transfer', + queryId, + amount, + destination, + responseDestination, + customPayload, + forwardAmount, + forwardPayload, + }; + } + case 0x5fcc3d14: { + const queryId = normalizeQueryId(s.loadUintBig(64)); + const newOwner = s.loadAddress(); + const responseDestination = s.loadAddress(); + const customPayload = s.loadMaybeRef(); + const forwardAmount = s.loadCoins(); + + let forwardPayload: Cell | null = null; + if (s.loadBit()) { + forwardPayload = s.loadRef(); + } else { + const p = s.asCell(); + s = new Cell().beginParse(); // clear the slice + if (!p.hash().equals(new Cell().hash())) { + if (params.disallowModification) { + throw new Error('Jetton transfer message would be modified'); + } + forwardPayload = p; + } + } + + s.endParse(); + + return { + type: 'nft-transfer', + queryId, + newOwner, + responseDestination, + customPayload, + forwardAmount, + forwardPayload, + }; + } + case 0x595f07bc: { + const queryId = normalizeQueryId(s.loadUintBig(64)); + const amount = s.loadCoins(); + const responseDestination = s.loadAddress(); + let customPayload: Cell | Buffer | null = s.loadMaybeRef(); + s.endParse(); + + if (params.encodeJettonBurnEthAddressAsHex && customPayload !== null && customPayload.bits.length === 160 && customPayload.refs.length === 0) { + const cs = customPayload.beginParse(); + customPayload = cs.loadBuffer(20); + cs.endParse(); + } + + return { + type: 'jetton-burn', + queryId, + amount, + responseDestination, + customPayload, + }; + } + case 0x7258a69b: { + const queryId = normalizeQueryId(s.loadUintBig(64)); + const address = s.loadAddress(); + s.endParse(); + + return { + type: 'add-whitelist', + queryId, + address, + }; + } + case 0x1000: { + const queryId = normalizeQueryId(s.loadUintBig(64)); + const amount = s.loadCoins(); + s.endParse(); + + return { + type: 'single-nominator-withdraw', + queryId, + amount, + }; + } + case 0x1001: { + const queryId = normalizeQueryId(s.loadUintBig(64)); + const address = s.loadAddress(); + s.endParse(); + + return { + type: 'single-nominator-change-validator', + queryId, + address, + }; + } + case 0x47d54391: { + const queryId = normalizeQueryId(s.loadUintBig(64)); + let appId: bigint | null = null; + if (s.remainingBits > 0) { + appId = s.loadUintBig(64); + } + s.endParse(); + + return { + type: 'tonstakers-deposit', + queryId, + appId, + }; + } + case 0x69fb306c: { + const queryId = normalizeQueryId(s.loadUintBig(64)); + const votingAddress = s.loadAddress(); + const expirationDate = s.loadUint(48); + const vote = s.loadBit(); + const needConfirmation = s.loadBit(); + s.endParse(); + + return { + type: 'vote-for-proposal', + queryId, + votingAddress, + expirationDate, + vote, + needConfirmation, + }; + } + case 0x4eb1f0f9: { + const queryId = normalizeQueryId(s.loadUintBig(64)); + const key = s.loadBuffer(32); + + if (key.equals(dnsWalletKey)) { + if (s.remainingRefs > 0) { + const vs = s.loadRef().beginParse(); + if (s.remainingBits > 0 && !params.disallowModification) { + // tolerate the Maybe bit + if (!s.loadBit()) throw new Error('Incorrect change DNS record message'); + } + s.endParse(); + + const type = vs.loadUint(16); + if (type !== 0x9fd3) { + throw new Error('Wrong DNS record type'); + } + + const address = vs.loadAddress(); + const flags = vs.loadUint(8); + if (flags > 1) { + throw new Error('DNS wallet record must have flags 0 or 1'); + } + let capabilities: { isWallet: boolean } | null = (flags & 1) > 0 ? { isWallet: false } : null; + if (capabilities !== null) { + while (vs.loadBit()) { + const cap = vs.loadUint(16); + if (cap === 0x2177) { + if (capabilities.isWallet && params.disallowModification) { + throw new Error('DNS change record message would be modified'); + } + capabilities.isWallet = true; + } else { + throw new Error('Unknown DNS wallet record capability'); + } + } + } + + return { + type: 'change-dns-record', + queryId, + record: { + type: 'wallet', + value: { + address, + capabilities, + }, + }, + }; + } else { + if (s.remainingBits > 0 && !params.disallowModification) { + // tolerate the Maybe bit + if (s.loadBit()) throw new Error('Incorrect change DNS record message'); + } + s.endParse(); + + return { + type: 'change-dns-record', + queryId, + record: { + type: 'wallet', + value: null, + }, + }; + } + } else { + if (s.remainingRefs > 0) { + const value = s.loadRef(); + if (s.remainingBits > 0 && !params.disallowModification) { + // tolerate the Maybe bit + if (!s.loadBit()) throw new Error('Incorrect change DNS record message'); + } + s.endParse(); + + return { + type: 'change-dns-record', + queryId, + record: { + type: 'unknown', + key, + value, + }, + }; + } else { + if (s.remainingBits > 0 && !params.disallowModification) { + // tolerate the Maybe bit + if (s.loadBit()) throw new Error('Incorrect change DNS record message'); + } + s.endParse(); + + return { + type: 'change-dns-record', + queryId, + record: { + type: 'unknown', + key, + value: null, + }, + }; + } + } + } + case 0x8: { + const queryId = normalizeQueryId(s.loadUintBig(64)); + const swapId = s.loadBuffer(32); + s.endParse(); + + return { + type: 'token-bridge-pay-swap', + queryId, + swapId, + }; + } + } + throw new Error('Unknown op: ' + op); + } catch (e) { + if (params.disallowUnsafe) { + throw e; + } + } + + return { + type: 'unsafe', + message: cell, + }; +} + export type SignDataRequest = | { type: 'plaintext', text: string } | { type: 'app-data', address?: Address, domain?: string, data: Cell, ext?: Cell } @@ -816,6 +1139,17 @@ export class TonTransport { .endCell(); } + async getSettings(): Promise<{ + blindSigningEnabled: boolean + expertMode: boolean + }> { + let loaded = await this.#doRequest(INS_SETTINGS, 0x00, 0x00, Buffer.alloc(0)); + return { + blindSigningEnabled: (loaded[0] & 0x01) > 0, + expertMode: (loaded[0] & 0x02) > 0, + }; + } + #doRequest = async (ins: number, p1: number, p2: number, data: Buffer) => { return this.#lock.inLock(async () => { let r = await this.transport.send( diff --git a/source/index.ts b/source/index.ts index e313448..578692f 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1 +1 @@ -export { TonPayloadFormat, TonTransport, SignDataRequest } from './TonTransport'; \ No newline at end of file +export { TonPayloadFormat, TonTransport, SignDataRequest, parseMessage } from './TonTransport'; \ No newline at end of file From 06ecd0d6c4796444f7a32f9f06aa68affb676eb3 Mon Sep 17 00:00:00 2001 From: krigga Date: Thu, 25 Jul 2024 17:41:50 +0300 Subject: [PATCH 3/6] feat: hardcoded jettons --- package.json | 2 +- source/TonTransport.ts | 95 +++++++++++++++++++++++++++++++++++------ source/index.ts | 2 +- source/utils/getInit.ts | 14 +++--- 4 files changed, 90 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index a23a972..8f5bc26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ton-community/ton-ledger", - "version": "7.1.0-pre.1", + "version": "7.2.0-pre.1", "repository": "https://github.com/ton-community/ton-ledger-ts", "author": "Steve Korshakov ", "license": "MIT", diff --git a/source/TonTransport.ts b/source/TonTransport.ts index 71f54b4..56d9ddb 100644 --- a/source/TonTransport.ts +++ b/source/TonTransport.ts @@ -16,10 +16,42 @@ const INS_SETTINGS = 0x0A; const DEFAULT_SUBWALLET_ID = 698983191; +export type KnownJetton = { + symbol: string; + masterAddress: Address; +}; + +export const KNOWN_JETTONS: KnownJetton[] = [ + { + symbol: 'USDT', + masterAddress: Address.parse('EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs'), + }, + { + symbol: 'NOT', + masterAddress: Address.parse('EQAvlWFDxGF2lXm67y4yzC17wYKD9A0guwPkMs1gOsM__NOT'), + }, + { + symbol: 'tsTON', + masterAddress: Address.parse('EQC98_qAmNEptUtPc7W6xdHh_ZHrBUFpw5Ft_IzNU20QAJav'), + }, + { + symbol: 'wsTON', + masterAddress: Address.parse('EQB0SoxuGDx5qjVt0P_bPICFeWdFLBmVopHhjgfs0q-wsTON'), + }, + { + symbol: 'hTON', + masterAddress: Address.parse('EQDPdq8xjAhytYqfGSX8KcFWIReCufsB9Wdg0pLlYSO_h76w'), + }, + { + symbol: 'stTON', + masterAddress: Address.parse('EQDNhy-nxYFgUqzfUzImBEP67JqsyMIcyk2S5_RwNNEYku0k'), + }, +]; + export type TonPayloadFormat = | { type: 'unsafe', message: Cell } | { type: 'comment', text: string } - | { type: 'jetton-transfer', queryId: bigint | null, amount: bigint, destination: Address, responseDestination: Address, customPayload: Cell | null, forwardAmount: bigint, forwardPayload: Cell | null } + | { type: 'jetton-transfer', queryId: bigint | null, amount: bigint, destination: Address, responseDestination: Address, customPayload: Cell | null, forwardAmount: bigint, forwardPayload: Cell | null, knownJetton: { jettonId: number, workchain: number } | null } | { type: 'nft-transfer', queryId: bigint | null, newOwner: Address, responseDestination: Address, customPayload: Cell | null, forwardAmount: bigint, forwardPayload: Cell | null } | { type: 'jetton-burn', queryId: bigint | null, amount: bigint, responseDestination: Address, customPayload: Cell | Buffer | null } | { type: 'add-whitelist', queryId: bigint | null, address: Address } @@ -105,6 +137,7 @@ export function parseMessage(cell: Cell, opts?: { disallowUnsafe?: boolean, disa customPayload, forwardAmount, forwardPayload, + knownJetton: null, }; } case 0x5fcc3d14: { @@ -365,10 +398,13 @@ function chunks(buf: Buffer, n: number): Buffer[] { return cs; } -function processAddressFlags(opts?: { testOnly?: boolean, bounceable?: boolean, chain?: number }): { testOnly: boolean, bounceable: boolean, chain: number, flags: number } { +function processAddressFlags(opts?: { testOnly?: boolean, bounceable?: boolean, chain?: number, subwalletId?: number, walletVersion?: 'v3r2' | 'v4' }): { testOnly: boolean, bounceable: boolean, chain: number, flags: number, specifiers?: { subwalletId: number, isV3R2: boolean } } { const bounceable = opts?.bounceable ?? true; const testOnly = opts?.testOnly ?? false; const chain = opts?.chain ?? 0; + const subwalletId = opts?.subwalletId ?? 698983191; + const walletVersion = opts?.walletVersion ?? 'v3r2'; + let specifiers: { subwalletId: number, isV3R2: boolean } | undefined = undefined; let flags = 0x00; if (testOnly) { @@ -377,8 +413,15 @@ function processAddressFlags(opts?: { testOnly?: boolean, bounceable?: boolean, if (chain === -1) { flags |= 0x02; } + if (subwalletId !== 698983191 || walletVersion !== 'v4') { + flags |= 0x04; + specifiers = { + subwalletId, + isV3R2: walletVersion === 'v3r2', + }; + } - return { bounceable, testOnly, chain, flags }; + return { bounceable, testOnly, chain, flags, specifiers }; } function convertPayload(input: TonPayloadFormat | undefined): { payload: Cell | null, hints: Buffer } { @@ -421,15 +464,28 @@ function convertPayload(input: TonPayloadFormat | undefined): { payload: Cell | .storeUint(input.type === 'jetton-transfer' ? 0x0f8a7ea5 : 0x5fcc3d14, 32); let d = Buffer.alloc(0); + let flags = 0; if (input.queryId !== null) { - d = Buffer.concat([d, writeUint8(1), writeUint64(input.queryId)]); + flags |= 1; + } + if (input.type === 'jetton-transfer' && input.knownJetton !== null) { + flags |= 2; + } + + d = Buffer.concat([d, writeUint8(flags)]); + + if (input.queryId !== null) { + d = Buffer.concat([d, writeUint64(input.queryId)]); b = b.storeUint(input.queryId, 64); } else { - d = Buffer.concat([d, writeUint8(0)]); b = b.storeUint(0, 64); } if (input.type === 'jetton-transfer') { + if (input.knownJetton !== null) { + d = Buffer.concat([d, writeUint16(input.knownJetton.jettonId), writeUint8(input.knownJetton.workchain)]); + } + d = Buffer.concat([d, writeVarUInt(input.amount)]); b = b.storeCoins(input.amount); @@ -786,13 +842,13 @@ export class TonTransport { // Operations // - async getAddress(path: number[], opts?: { testOnly?: boolean, bounceable?: boolean, chain?: number }) { + async getAddress(path: number[], opts?: { testOnly?: boolean, bounceable?: boolean, chain?: number, subwalletId?: number, walletVersion?: 'v3r2' | 'v4' }) { // Check path validatePath(path); // Resolve flags - const { bounceable, testOnly, chain } = processAddressFlags(opts); + const { bounceable, testOnly, chain, specifiers } = processAddressFlags(opts); // Get public key let response = await this.#doRequest(INS_ADDRESS, 0x00, 0x00, pathElementsToBuffer(path.map((v) => v + 0x80000000))); @@ -801,34 +857,39 @@ export class TonTransport { } // Contract - const contract = getInit(chain, response); + const contract = getInit(response, specifiers?.subwalletId ?? 698983191, specifiers?.isV3R2 ?? false); const address = contractAddress(chain, contract); return { address: address.toString({ bounceable, testOnly }), publicKey: response }; } - async validateAddress(path: number[], opts?: { testOnly?: boolean, bounceable?: boolean, chain?: number }) { + async validateAddress(path: number[], opts?: { testOnly?: boolean, bounceable?: boolean, chain?: number, subwalletId?: number, walletVersion?: 'v3r2' | 'v4' }) { // Check path validatePath(path); // Resolve flags - const { bounceable, testOnly, chain, flags } = processAddressFlags(opts); + const { bounceable, testOnly, chain, flags, specifiers } = processAddressFlags(opts); + + let r = pathElementsToBuffer(path.map((v) => v + 0x80000000)); + if (specifiers !== undefined) { + r = Buffer.concat([r, writeUint8(specifiers.isV3R2 ? 1 : 0), writeUint32(specifiers.subwalletId)]); + } // Get public key - let response = await this.#doRequest(INS_ADDRESS, 0x01, flags, pathElementsToBuffer(path.map((v) => v + 0x80000000))); + let response = await this.#doRequest(INS_ADDRESS, 0x01, flags, r); if (response.length !== 32) { throw Error('Invalid response'); } // Contract - const contract = getInit(chain, response); + const contract = getInit(response, specifiers?.subwalletId ?? 698983191, specifiers?.isV3R2 ?? false); const address = contractAddress(chain, contract); return { address: address.toString({ bounceable, testOnly }), publicKey: response }; } - async getAddressProof(path: number[], params: { domain: string, timestamp: number, payload: Buffer }, opts?: { testOnly?: boolean, bounceable?: boolean, chain?: number }) { + async getAddressProof(path: number[], params: { domain: string, timestamp: number, payload: Buffer }, opts?: { testOnly?: boolean, bounceable?: boolean, chain?: number, subwalletId?: number, walletVersion?: 'v3r2' | 'v4' }) { // Check path validatePath(path); @@ -836,11 +897,17 @@ export class TonTransport { let publicKey = (await this.getAddress(path)).publicKey; // Resolve flags - const { flags } = processAddressFlags(opts); + const { flags, specifiers } = processAddressFlags(opts); + + let specifiersBuf = Buffer.alloc(0); + if (specifiers !== undefined) { + specifiersBuf = Buffer.concat([writeUint8(specifiers.isV3R2 ? 1 : 0), writeUint32(specifiers.subwalletId)]); + } const domainBuf = Buffer.from(params.domain, 'utf-8'); const reqBuf = Buffer.concat([ pathElementsToBuffer(path.map((v) => v + 0x80000000)), + specifiersBuf, writeUint8(domainBuf.length), domainBuf, writeUint64(BigInt(params.timestamp)), diff --git a/source/index.ts b/source/index.ts index 578692f..e230fc0 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1 +1 @@ -export { TonPayloadFormat, TonTransport, SignDataRequest, parseMessage } from './TonTransport'; \ No newline at end of file +export { TonPayloadFormat, TonTransport, SignDataRequest, parseMessage, KNOWN_JETTONS } from './TonTransport'; \ No newline at end of file diff --git a/source/utils/getInit.ts b/source/utils/getInit.ts index a32adbb..a85cd48 100644 --- a/source/utils/getInit.ts +++ b/source/utils/getInit.ts @@ -1,12 +1,12 @@ import { Cell, beginCell } from '@ton/core'; -export function getInit(workchain: number, publicKey: Buffer) { - let code = Cell.fromBoc(Buffer.from('te6ccgECFAEAAtQAART/APSkE/S88sgLAQIBIAIDAgFIBAUE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8QERITAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNBgcCASAICQB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAGAIpQBIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UAXKwjiOCEGRzdHKDHrFwgBhQBcsFUAPPFiP6AhPLassfyz/JgED7AJJfA+ICASAKCwBZvSQrb2omhAgKBrkPoCGEcNQICEekk30pkQzmkD6f+YN4EoAbeBAUiYcVnzGEAgFYDA0AEbjJftRNDXCx+AA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIA4PABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AAG7SB/oA1NQi+QAFyMoHFcv/ydB3dIAYyMsFywIizxZQBfoCFMtrEszMyXP7AMhAFIEBCPRR8qcCAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAAr0AMntVA==', 'base64'))[0]; +export function getInit(publicKey: Buffer, subwalletId: number, isV3R2: boolean) { let data = beginCell() .storeUint(0, 32) // Seqno - .storeUint(698983191 + workchain, 32) - .storeBuffer(publicKey) - .storeBit(0) // Empty plugins dict - .endCell(); - return { code, data }; + .storeUint(subwalletId, 32) + .storeBuffer(publicKey); + return { + code: isV3R2 ? Cell.fromBase64('te6cckEBAQEAcQAA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVBC9ba0=') : Cell.fromBase64('te6ccgECFAEAAtQAART/APSkE/S88sgLAQIBIAIDAgFIBAUE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8QERITAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNBgcCASAICQB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAGAIpQBIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UAXKwjiOCEGRzdHKDHrFwgBhQBcsFUAPPFiP6AhPLassfyz/JgED7AJJfA+ICASAKCwBZvSQrb2omhAgKBrkPoCGEcNQICEekk30pkQzmkD6f+YN4EoAbeBAUiYcVnzGEAgFYDA0AEbjJftRNDXCx+AA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIA4PABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AAG7SB/oA1NQi+QAFyMoHFcv/ydB3dIAYyMsFywIizxZQBfoCFMtrEszMyXP7AMhAFIEBCPRR8qcCAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAAr0AMntVA=='), + data: isV3R2 ? data.endCell() : data.storeBit(0).endCell(), + }; } \ No newline at end of file From 539bb935730508856639d7e2fae5ed74f0eebf94 Mon Sep 17 00:00:00 2001 From: krigga Date: Thu, 25 Jul 2024 18:18:41 +0300 Subject: [PATCH 4/6] feat: add STAKED jetton --- package.json | 2 +- source/TonTransport.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 8f5bc26..c94d102 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ton-community/ton-ledger", - "version": "7.2.0-pre.1", + "version": "7.2.0-pre.2", "repository": "https://github.com/ton-community/ton-ledger-ts", "author": "Steve Korshakov ", "license": "MIT", diff --git a/source/TonTransport.ts b/source/TonTransport.ts index 56d9ddb..b241ec4 100644 --- a/source/TonTransport.ts +++ b/source/TonTransport.ts @@ -46,6 +46,10 @@ export const KNOWN_JETTONS: KnownJetton[] = [ symbol: 'stTON', masterAddress: Address.parse('EQDNhy-nxYFgUqzfUzImBEP67JqsyMIcyk2S5_RwNNEYku0k'), }, + { + symbol: 'STAKED', + masterAddress: Address.parse('EQCqC6EhRJ_tpWngKxL6dV0k6DSnRUrs9GSVkLbfdCqsj6TE'), + }, ]; export type TonPayloadFormat = From 477bd2041dabf425815069bc4cf0c0d6a75e4364 Mon Sep 17 00:00:00 2001 From: krigga Date: Thu, 8 Aug 2024 12:33:01 +0300 Subject: [PATCH 5/6] fix: default wallet version --- package.json | 2 +- source/TonTransport.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c94d102..f8aeb9e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ton-community/ton-ledger", - "version": "7.2.0-pre.2", + "version": "7.2.0-pre.3", "repository": "https://github.com/ton-community/ton-ledger-ts", "author": "Steve Korshakov ", "license": "MIT", diff --git a/source/TonTransport.ts b/source/TonTransport.ts index b241ec4..f208ba0 100644 --- a/source/TonTransport.ts +++ b/source/TonTransport.ts @@ -407,7 +407,7 @@ function processAddressFlags(opts?: { testOnly?: boolean, bounceable?: boolean, const testOnly = opts?.testOnly ?? false; const chain = opts?.chain ?? 0; const subwalletId = opts?.subwalletId ?? 698983191; - const walletVersion = opts?.walletVersion ?? 'v3r2'; + const walletVersion = opts?.walletVersion ?? 'v4'; let specifiers: { subwalletId: number, isV3R2: boolean } | undefined = undefined; let flags = 0x00; From 78b02c5287f5f149aab4b4b2c79fccb172b368c4 Mon Sep 17 00:00:00 2001 From: krigga <25533192+krigga@users.noreply.github.com> Date: Fri, 7 Feb 2025 01:19:45 +0300 Subject: [PATCH 6/6] chore: bump version, changelog --- CHANGELOG.md | 20 ++++++++++++++++++-- package.json | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c83baf8..bccc46e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.7.1] - 2024-01-17 +## [7.2.0] - 2025-02-07 + +- Added support for TON Ledger App 2.2.0 features, including: +- Hardcoded jettons +- Wallet specifiers support in more methods + +## [7.1.0] - Not released + +### Added + +- Added `parseMessage` helper to parse messages into TON Ledger App format +- Added support for TON Ledger App 2.1.0 features, including: +- New message types +- Wallet specifiers (subwallet ID and wallet op inclusion for v4 support) +- `getSettings` method + +## [7.0.1] - 2024-01-17 ### Fixed - Fixed incorrect VarUInt encoding of 0 (0 nanoTON, 0 jetton units, etc) -## [0.7.0] - 2023-09-15 +## [7.0.0] - 2023-09-15 ### Changed diff --git a/package.json b/package.json index f8aeb9e..4d1f88e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ton-community/ton-ledger", - "version": "7.2.0-pre.3", + "version": "7.2.0", "repository": "https://github.com/ton-community/ton-ledger-ts", "author": "Steve Korshakov ", "license": "MIT",