diff --git a/package.json b/package.json index 7d04260..9a542a7 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "io-ts": "^2.2.16", "teslabot": "^1.5.0", "ton": "9.8.0", - "ton-contracts": "^3.0.0", "ton-crypto": "^3.0.0" } } diff --git a/src/connector/TonhubConnector.ts b/src/connector/TonhubConnector.ts index f7761fc..d5a4596 100644 --- a/src/connector/TonhubConnector.ts +++ b/src/connector/TonhubConnector.ts @@ -5,8 +5,8 @@ import * as t from 'io-ts'; import { delay } from "teslabot"; import { Cell, Address, beginCell, CommentMessage, safeSign, contractAddress, safeSignVerify } from 'ton'; import BN from 'bn.js'; -import { WalletV4Source } from 'ton-contracts'; import { TonhubHttpTransport } from '../transport/TonhubHttpTransport'; +import { WalletV4Source } from "../contracts/WalletV4Source"; const sessionStateCodec = t.union([ t.type({ diff --git a/src/contracts/WalletV4Contract.ts b/src/contracts/WalletV4Contract.ts new file mode 100644 index 0000000..e04fec3 --- /dev/null +++ b/src/contracts/WalletV4Contract.ts @@ -0,0 +1,114 @@ +import { Address, Cell, Contract, contractAddress, InternalMessage, Message, TonClient } from "ton"; +import { sign } from "ton-crypto"; +import { Maybe } from "ton/dist/types"; +import { WalletV4Source } from "./WalletV4Source"; + +class WalletV4SigningMessage implements Message { + + readonly timeout: number; + readonly seqno: number; + readonly walletId: number; + readonly order: Message | null; + readonly sendMode: number; + + constructor(args: { timeout?: Maybe, seqno: Maybe, walletId?: number, sendMode: number, order: Message | null }) { + this.order = args.order; + this.sendMode = args.sendMode; + if (args.timeout !== undefined && args.timeout !== null) { + this.timeout = args.timeout; + } else { + this.timeout = Math.floor(Date.now() / 1e3) + 60; // Default timeout: 60 seconds + } + if (args.seqno !== undefined && args.seqno !== null) { + this.seqno = args.seqno; + } else { + this.seqno = 0; + } + if (args.walletId !== null && args.walletId !== undefined) { + this.walletId = args.walletId; + } else { + this.walletId = 698983191; + } + } + + writeTo(cell: Cell) { + cell.bits.writeUint(this.walletId, 32); + if (this.seqno === 0) { + for (let i = 0; i < 32; i++) { + cell.bits.writeBit(1); + } + } else { + cell.bits.writeUint(this.timeout, 32); + } + cell.bits.writeUint(this.seqno, 32); + cell.bits.writeUint8(0); // Simple order + + // Write order + if (this.order) { + cell.bits.writeUint8(this.sendMode); + let orderCell = new Cell(); + this.order.writeTo(orderCell); + cell.refs.push(orderCell); + } + } +} + +export class WalletV4Contract implements Contract { + + static create(source: WalletV4Source) { + let address = contractAddress(source); + return new WalletV4Contract(address, source); + } + + readonly address: Address; + readonly source: WalletV4Source; + + constructor(address: Address, source: WalletV4Source) { + this.address = address; + this.source = source; + } + + async getSeqNo(client: TonClient) { + if (await client.isContractDeployed(this.address)) { + let res = await client.callGetMethod(this.address, 'seqno'); + return parseInt(res.stack[0][1], 16); + } else { + return 0; + } + } + + async createTransfer(args: { + seqno: number, + sendMode: number, + walletId: number, + order: InternalMessage | null, + secretKey?: Maybe, + timeout?: Maybe + }) { + + let signingMessage = new WalletV4SigningMessage({ + timeout: args.timeout, + walletId: args.walletId, + seqno: args.seqno, + sendMode: args.sendMode, + order: args.order + }); + + // Sign message + const cell = new Cell(); + signingMessage.writeTo(cell); + let signature: Buffer; + if (args.secretKey) { + signature = sign(cell.hash(), args.secretKey); + } else { + signature = Buffer.alloc(64); + } + + // Body + const body = new Cell(); + body.bits.writeBuffer(signature); + signingMessage.writeTo(body); + + return body; + } +} \ No newline at end of file diff --git a/src/contracts/WalletV4Source.ts b/src/contracts/WalletV4Source.ts new file mode 100644 index 0000000..f823c5a --- /dev/null +++ b/src/contracts/WalletV4Source.ts @@ -0,0 +1,60 @@ +import { Cell, ConfigStore, ContractSource } from "ton"; +import { Maybe } from "ton/dist/types"; + +export class WalletV4Source implements ContractSource { + + static readonly SOURCE = 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' + ); + + static create(opts: { workchain: number, publicKey: Buffer, walletId?: Maybe }) { + + // Build initial code and data + const walletId = opts.walletId ? opts.walletId : 698983191; + let initialCode = Cell.fromBoc(WalletV4Source.SOURCE)[0]; + let initialData = new Cell(); + initialData.bits.writeUint(0, 32); + initialData.bits.writeUint(walletId, 32); + initialData.bits.writeBuffer(opts.publicKey); + initialData.bits.writeBit(0); + + return new WalletV4Source({ initialCode, initialData, workchain: opts.workchain, walletId, publicKey: opts.publicKey }); + } + + static restore(backup: string) { + const store = new ConfigStore(backup); + return WalletV4Source.create({ + workchain: store.getInt('wc'), + publicKey: store.getBuffer('pk'), + walletId: store.getInt('walletId'), + }); + } + + readonly initialCode: Cell; + readonly initialData: Cell; + readonly walletId: number; + readonly workchain: number; + readonly publicKey: Buffer; + readonly type = 'org.ton.wallets.v4'; + + private constructor(args: { initialCode: Cell, initialData: Cell, workchain: number, walletId: number, publicKey: Buffer }) { + this.initialCode = args.initialCode; + this.initialData = args.initialData; + this.workchain = args.workchain; + this.walletId = args.walletId; + this.publicKey = args.publicKey; + } + + describe() { + return 'Wallet v4 #' + this.walletId; + } + + backup() { + const config = new ConfigStore(); + config.setInt('wc', this.workchain); + config.setBuffer('pk', this.publicKey); + config.setInt('walletId', this.walletId); + return config.save(); + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index cf63a60..634140c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@openland/patterns@^0.0.2": - version "0.0.2" - resolved "https://registry.yarnpkg.com/@openland/patterns/-/patterns-0.0.2.tgz#ada0417c4e72aeeb461bf9d8759ecd9ca1ebf51d" - integrity sha512-ttgJELwtG8cIER1JeWjkIMg3e+MlAL6n4wFPSNPoBCl+5t3G/HIz9kolvxBmN5/3RXEgh68NnePzGiUBaUHmRQ== - "@scarf/scarf@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.1.1.tgz#d8b9f20037b3a37dbf8dcdc4b3b72f9285bfce35" @@ -157,13 +152,6 @@ teslabot@^1.3.0, teslabot@^1.5.0: resolved "https://registry.yarnpkg.com/teslabot/-/teslabot-1.5.0.tgz#70f544516699ca5f696d8ae94f3d12cd495d5cd6" integrity sha512-e2MmELhCgrgZEGo7PQu/6bmYG36IDH+YrBI1iGm6jovXkeDIGa3pZ2WSqRjzkuw2vt1EqfkZoV5GpXgqL8QJVg== -ton-contracts@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ton-contracts/-/ton-contracts-3.0.0.tgz#f3aa68e85fa963836d3aa0b67eae9196f1e67b94" - integrity sha512-62i/fyFQSTEV9PUFiFd74L9CSWLRBxbuDp7nPzNYTaWBFR+ijTqKand+bfEZLIkNOUoY4jlrEogO5ZnNhYq1Zw== - dependencies: - "@openland/patterns" "^0.0.2" - ton-crypto-primitives@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ton-crypto-primitives/-/ton-crypto-primitives-2.0.0.tgz#e85cd68c0d523f6bdf3f306201a76e51b7e9312e"