· 4 years ago · Apr 04, 2021, 04:00 PM
1import { Injectable } from '@nestjs/common';
2import {
3 NetworkFee,
4 Transaction,
5 TransactionTypeEnum,
6} from './entities/transaction.entity';
7// Pure JS using
8// eslint-disable-next-line @typescript-eslint/no-var-requires
9const TonWeb = require('tonweb');
10
11@Injectable()
12export class TransactionService {
13 private tonweb: any;
14 private nacl: any;
15 constructor() {
16 this.tonweb = new TonWeb();
17 this.nacl = TonWeb.utils.nacl;
18 }
19 async getTransactions(address: string): Promise<Array<Transaction>> {
20 const history = await this.tonweb.getTransactions(address);
21 const transactions = new Array<Transaction>();
22 for (let i = 0; i < history.length; i++) {
23 const transaction: any = history[i];
24 const transactionType =
25 transaction.out_msgs.length > 0
26 ? TransactionTypeEnum.OUT
27 : TransactionTypeEnum.IN;
28 let source;
29 let destination;
30 let value;
31 switch (transactionType) {
32 case TransactionTypeEnum.IN:
33 source = transaction['in_msg']['source'];
34 destination = address;
35 value = parseInt(transaction['in_msg']['value']);
36 break;
37 case TransactionTypeEnum.OUT:
38 source = address;
39 destination = transaction['out_msgs'][0]['destination'];
40 value = parseInt(transaction['out_msgs'][0]['value']);
41 break;
42 default:
43 throw new Error('Not implemented error');
44 }
45 transactions.push({
46 uTime: transaction['utime'],
47 id: {
48 type: transaction['transaction_id']['@type'],
49 lt: transaction['transaction_id']['lt'],
50 hash: transaction['transaction_id']['hash'],
51 },
52 type: transactionType,
53 value: value,
54 fee: parseInt(transaction['fee']),
55 source: source,
56 destination: destination,
57 });
58 }
59 return transactions;
60 }
61 async getLastTransaction(address: string): Promise<Transaction | null> {
62 let transactions = await this.getTransactions(address);
63 if (transactions.length == 0) {
64 return null;
65 }
66 transactions = transactions.sort((a, b) => (a.uTime > b.uTime ? 1 : -1));
67 return transactions[transactions.length - 1];
68 }
69 async makeTransaction(
70 secretKeyHex: string,
71 destination: string,
72 amount: number,
73 depth = 0,
74 ): Promise<Transaction> {
75 const secretKey = TonWeb.utils.hexToBytes(secretKeyHex);
76 const keyPair = this.nacl.sign.keyPair.fromSecretKey(secretKey);
77 const wallet = this.tonweb.wallet.create({ publicKey: keyPair.publicKey });
78 await wallet.deploy(secretKey).send();
79 const sourceAddress = (await wallet.getAddress()).toString(
80 true,
81 true,
82 false,
83 );
84 const sourceLastTransaction = await this.getLastTransaction(sourceAddress);
85 const seqno = await wallet.methods.seqno().call();
86 const transfer = wallet.methods.transfer({
87 secretKey: keyPair.secretKey,
88 toAddress: destination,
89 amount: TonWeb.utils.toNano(amount),
90 seqno: seqno,
91 payload: 'Check',
92 sendMode: 3,
93 });
94 const transferSent = await transfer.send();
95 if (transferSent['@type'] != 'ok') {
96 throw new Error('Transfer sent error.');
97 }
98 const timeoutMs = 60 * 1000;
99 const relaxTimeMs = 5 * 1000;
100 const relaxTimeRecursionMs = 10 * 1000;
101 const start = Date.now();
102 let lastTransaction: any = null;
103 do {
104 lastTransaction = await this.getLastTransaction(sourceAddress);
105 if (lastTransaction !== null) {
106 if (
107 sourceLastTransaction === null ||
108 sourceLastTransaction.uTime !== lastTransaction.uTime
109 ) {
110 break;
111 }
112 }
113 await new Promise((r) => setTimeout(r, relaxTimeMs));
114 } while (Date.now() - start <= timeoutMs);
115 if (lastTransaction === null) {
116 throw new Error('Transaction retrieving timeout.');
117 }
118 if (lastTransaction['value'] === 0) {
119 if (depth > 0) {
120 throw new Error('Transaction value = 0.');
121 }
122 await new Promise((r) => setTimeout(r, relaxTimeRecursionMs));
123 depth++;
124 return await this.makeTransaction(
125 secretKeyHex,
126 destination,
127 amount,
128 depth,
129 );
130 } else {
131 return lastTransaction;
132 }
133 }
134 async getNetworkFee(amountNano: number): Promise<NetworkFee> {
135 const secretKeyHex =
136 'c1e01bb4c3ab38139b4d0bd3665676fa48217fb52448d19d57b2dc78896e3f3536f1a3136f4e6e223b4aaf83c52384de42c58a611e0c770504297798f2633f22';
137 const destination = 'EQCUqJmbKnzMZPvC0JW3UxKfHPT37OtREMZQRf4m9xzNivpV';
138 const secretKey = TonWeb.utils.hexToBytes(secretKeyHex);
139 const keyPair = this.nacl.sign.keyPair.fromSecretKey(secretKey);
140 const wallet = this.tonweb.wallet.create({ publicKey: keyPair.publicKey });
141 const seqno = await wallet.methods.seqno().call();
142 const transfer: any = wallet.methods.transfer({
143 secretKey: keyPair.secretKey,
144 toAddress: destination,
145 amount: amountNano,
146 seqno: seqno,
147 payload: 'Check',
148 sendMode: 3,
149 });
150 const transferFee = await transfer.estimateFee();
151 const inFwdFee = transferFee['source_fees']['in_fwd_fee'];
152 const storageFee = transferFee['source_fees']['storage_fee'];
153 const gasFee = transferFee['source_fees']['gas_fee'];
154 const fwdFee = transferFee['source_fees']['fwd_fee'];
155 const totalFee = inFwdFee + storageFee + gasFee + fwdFee;
156 return {
157 inFwdFee: inFwdFee,
158 storageFee: storageFee,
159 gasFee: gasFee,
160 fwdFee: fwdFee,
161 totalFee: totalFee,
162 };
163 }
164}
165