@instadapp/interop-x 0.0.0-dev.a168c79 → 0.0.0-dev.adea608

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,121 @@
1
+ import { BaseTask } from "../BaseTask";
2
+ import Logger from '@/logger';
3
+ import { ethers } from "ethers";
4
+ import abi from "@/abi";
5
+ import { Transaction } from "@/db";
6
+ import { generateInteropTransactionHash, getContract, getRpcProviderUrl } from "@/utils";
7
+ import { ChainId } from "@/types";
8
+ import config from "@/config";
9
+ import { InteropBridgeToken } from "@/typechain";
10
+
11
+ class SyncWithdrawEvents extends BaseTask {
12
+ contractAddress: string;
13
+ provider: ethers.providers.JsonRpcProvider;
14
+ contract: InteropBridgeToken;
15
+ chainId: ChainId;
16
+ itokenAddress: string;
17
+
18
+ constructor({ chainId, itokenAddress }: { chainId: ChainId, itokenAddress: string }) {
19
+ super({
20
+ logger: new Logger("InteropBridgeToken::SyncWithdrawEvents"),
21
+ })
22
+ this.chainId = chainId;
23
+ this.itokenAddress = itokenAddress;
24
+ }
25
+
26
+ async pollHandler() {
27
+ const currentBlock = await this.provider.getBlockNumber();
28
+
29
+ const events = await this.contract.queryFilter(
30
+ this.contract.filters.Burn(),
31
+ currentBlock - 2000,
32
+ currentBlock,
33
+ );
34
+
35
+ let processedEvents = 0;
36
+
37
+ for (const event of events) {
38
+
39
+ try {
40
+ if (!event.args) {
41
+ continue;
42
+ }
43
+
44
+ const { to, amount, chainId } = event.args;
45
+
46
+ const uniqueIdentifier = {
47
+ action: 'withdraw',
48
+ submitTransactionHash: event.transactionHash,
49
+ sourceChainId:this.chainId,
50
+ targetChainId: chainId.toNumber(),
51
+ }
52
+
53
+ if (await Transaction.findOne({ where: uniqueIdentifier })) {
54
+ continue;
55
+ }
56
+
57
+ const tx = await event.getTransaction()
58
+
59
+ await Transaction.create({
60
+ ...uniqueIdentifier,
61
+ transactionHash: generateInteropTransactionHash(uniqueIdentifier),
62
+ from: tx.from,
63
+ to,
64
+
65
+
66
+ submitTransactionHash: event.transactionHash,
67
+ submitBlockNumber: event.blockNumber,
68
+
69
+ // submit & source are the same
70
+ sourceTransactionHash: event.transactionHash,
71
+ sourceBlockNumber: event.blockNumber,
72
+ sourceStatus: "success",
73
+
74
+ targetStatus: "uninitialised",
75
+
76
+ submitEvent: {
77
+ to,
78
+ amount: amount.toString(),
79
+ itoken: this.itokenAddress,
80
+ chainId: chainId.toString()
81
+ },
82
+
83
+ sourceEvent: {
84
+ to,
85
+ amount: amount.toString(),
86
+ itoken: this.itokenAddress,
87
+ chainId: chainId.toString(),
88
+ },
89
+ status: "pending",
90
+ })
91
+
92
+ this.logger.info(
93
+ `Withdraw queued: ${event.transactionHash} ${event.blockNumber}`
94
+ );
95
+ } catch (error) {
96
+ this.logger.error(error);
97
+ }
98
+ }
99
+
100
+ if (processedEvents > 0)
101
+ this.logger.info(`${processedEvents} events processed`);
102
+ }
103
+
104
+ async start(): Promise<void> {
105
+ this.logger.info(`Starting execution watcher on interop chain`);
106
+
107
+ this.provider = new ethers.providers.JsonRpcProvider(
108
+ getRpcProviderUrl(this.chainId)
109
+ );
110
+
111
+ this.contract = getContract<InteropBridgeToken>(
112
+ this.itokenAddress,
113
+ abi.interopBridgeToken,
114
+ new ethers.Wallet(config.privateKey!, this.provider)
115
+ );
116
+
117
+ await super.start()
118
+ }
119
+ }
120
+
121
+ export default SyncWithdrawEvents;
@@ -3,7 +3,7 @@ import Logger from '@/logger';
3
3
  import { BigNumber, ethers } from "ethers";
4
4
  import abi from "@/abi";
5
5
  import { Transaction } from "@/db";
6
- import { buildDataForTransaction, buildSignatureBytes, getRpcProviderUrl, Signature } from "@/utils";
6
+ import { buildDataForTransaction, buildSignatureBytes, getContract, getRpcProviderUrl, Signature } from "@/utils";
7
7
  import { addresses } from "@/constants";
8
8
  import { ChainId } from "@/types";
9
9
  import config from "@/config";
@@ -13,7 +13,9 @@ import wait from "waait";
13
13
  import { peerPool, protocol } from "@/net";
14
14
  import { LogDescription } from "ethers/lib/utils";
15
15
 
16
- const generateGnosisTransaction = async (transactionData: any, safeContract: any) => {
16
+ const generateGnosisTransaction = async (transactionData: any, safeContract: GnosisSafe) => {
17
+ console.log(transactionData);
18
+
17
19
  let isExecuted = await safeContract.dataHashes(
18
20
  await safeContract.getTransactionHash(
19
21
  transactionData.to,
@@ -72,13 +74,21 @@ class ProcessDepositEvents extends BaseTask {
72
74
  where: {
73
75
  status: 'pending',
74
76
  sourceStatus: 'success',
77
+ targetStatus: 'uninitialised',
75
78
  action: 'deposit',
76
79
  sourceCreatedAt: {
77
80
  [Op.gte]: new Date(Date.now() - 12 * 60 * 60 * 1000),
78
81
  },
82
+ targetDelayUntil: {
83
+ [Op.or]: {
84
+ [Op.is]: null,
85
+ [Op.lt]: new Date(),
86
+ }
87
+ },
79
88
  sourceBlockNumber: {
80
89
  [Op.lt]: blockNumber - 12,
81
- }
90
+ },
91
+ sourceChainId: this.chainId,
82
92
  }
83
93
  })
84
94
 
@@ -86,11 +96,11 @@ class ProcessDepositEvents extends BaseTask {
86
96
  return;
87
97
  }
88
98
 
89
-
90
- transaction.sourceStatus = 'pending';
99
+ console.log(`Processing transaction ${transaction.transactionHash}`);
100
+
101
+ transaction.targetStatus = 'pending';
91
102
  await transaction.save();
92
103
 
93
-
94
104
  // refresh event data?
95
105
 
96
106
  const targetChainProvider = new ethers.providers.JsonRpcProvider(
@@ -101,19 +111,19 @@ class ProcessDepositEvents extends BaseTask {
101
111
 
102
112
  const safeAddress = addresses[transaction.targetChainId].gnosisSafe;
103
113
 
104
- const safeContract = new ethers.Contract(
114
+
115
+ const safeContract = getContract<GnosisSafe>(
105
116
  safeAddress,
106
117
  abi.gnosisSafe,
107
118
  targetWallet
108
- ) as GnosisSafe;
119
+ )
109
120
 
110
121
  const ownersThreshold = await safeContract.getThreshold();
111
-
112
122
  await wait(10000);
113
123
 
114
124
  let gnosisTx = await generateGnosisTransaction({
115
125
  baseGas: "0",
116
- data: buildDataForTransaction(transaction),
126
+ data: await buildDataForTransaction(transaction),
117
127
  gasPrice: "0",
118
128
  gasToken: "0x0000000000000000000000000000000000000000",
119
129
  nonce: '0',
@@ -131,6 +141,8 @@ class ProcessDepositEvents extends BaseTask {
131
141
 
132
142
  console.log(`Collecting signatures for execution ${transaction.transactionHash}`)
133
143
 
144
+ console.log(ownerPeerIds);
145
+
134
146
  const signatures = await protocol.requestSignatures({
135
147
  type: 'source',
136
148
  transactionHash: transaction.transactionHash,
@@ -145,33 +157,17 @@ class ProcessDepositEvents extends BaseTask {
145
157
 
146
158
  if (validSignatures.length === 0 || ownersThreshold.gt(validSignatures.length)) {
147
159
  await transaction.save();
148
- transaction.sourceDelayUntil = new Date(Date.now() + 30 * 1000);
149
- transaction.sourceStatus = 'pending'
160
+ transaction.targetDelayUntil = new Date(Date.now() + 30 * 1000);
161
+ transaction.targetStatus = 'uninitialised'
150
162
 
151
163
  await transaction.save();
152
164
  const errorMessage = signatures.find(s => !!s.error)?.error;
153
165
  throw new Error(`Not enough signatures` + (errorMessage ? `: ${errorMessage}` : ''));
154
166
  }
155
167
 
156
- const execTransactionParams = [
157
- gnosisTx.to,
158
- gnosisTx.value,
159
- gnosisTx.data,
160
- gnosisTx.operation,
161
- gnosisTx.safeTxGas,
162
- gnosisTx.baseGas,
163
- gnosisTx.gasPrice,
164
- gnosisTx.gasToken,
165
- gnosisTx.refundReceiver,
166
- buildSignatureBytes(validSignatures),
167
- ];
168
168
 
169
169
  console.log(`Executing transaction for execution ${transaction.transactionHash}`)
170
170
 
171
- console.log({
172
- execTransactionParams
173
- })
174
-
175
171
  const { data: txData } = await safeContract.populateTransaction.execTransaction(
176
172
  gnosisTx.to,
177
173
  gnosisTx.value,
@@ -185,10 +181,17 @@ class ProcessDepositEvents extends BaseTask {
185
181
  buildSignatureBytes(validSignatures)
186
182
  );
187
183
 
184
+ console.log({
185
+ from: targetWallet.address,
186
+ gasPrice: BigNumber.from(120 * 10 ** 9).toString(),
187
+ to: safeAddress,
188
+ data: txData,
189
+ })
190
+
191
+
188
192
  const txSent = await targetWallet.sendTransaction({
189
193
  from: targetWallet.address,
190
194
  gasPrice: BigNumber.from(120 * 10 ** 9),
191
- gasLimit: BigNumber.from(6_000_000),
192
195
  to: safeAddress,
193
196
  data: txData,
194
197
  })
@@ -205,9 +208,19 @@ class ProcessDepositEvents extends BaseTask {
205
208
 
206
209
  if (parsedLogs.find(e => e.name === 'ExecutionSuccess')) {
207
210
  console.log('ExecutionSuccess')
211
+ transaction.targetStatus = 'success'
212
+ transaction.targetTransactionHash = txSent.hash
213
+ transaction.status = 'success'
214
+ await transaction.save();
208
215
  } else {
209
216
  console.log('ExecutionFailure')
217
+ transaction.targetStatus = 'failed'
218
+ transaction.targetTransactionHash = txSent.hash
219
+ transaction.status = 'failed'
220
+ await transaction.save();
210
221
  }
222
+
223
+ protocol.sendTransaction(transaction)
211
224
  }
212
225
 
213
226
  async start(): Promise<void> {
@@ -219,11 +232,11 @@ class ProcessDepositEvents extends BaseTask {
219
232
  getRpcProviderUrl(this.chainId)
220
233
  );
221
234
 
222
- this.contract = new ethers.Contract(
235
+ this.contract = getContract<InteropXGateway>(
223
236
  this.contractAddress,
224
237
  abi.interopXGateway,
225
238
  new ethers.Wallet(config.privateKey!, this.provider)
226
- ) as InteropXGateway;
239
+ );
227
240
 
228
241
  await super.start()
229
242
  }
@@ -3,7 +3,7 @@ import Logger from '@/logger';
3
3
  import { ethers } from "ethers";
4
4
  import abi from "@/abi";
5
5
  import { Transaction } from "@/db";
6
- import { generateInteropTransactionHash, getRpcProviderUrl } from "@/utils";
6
+ import { generateInteropTransactionHash, getContract, getRpcProviderUrl } from "@/utils";
7
7
  import { addresses } from "@/constants";
8
8
  import { ChainId } from "@/types";
9
9
  import config from "@/config";
@@ -77,7 +77,7 @@ class SyncDepositEvents extends BaseTask {
77
77
  sourceChainId: sourceChainId.toString(),
78
78
  targetChainId: targetChainId.toString(),
79
79
  token: token,
80
- ammout: amount.toString(),
80
+ amount: amount.toString(),
81
81
  vnonce: vnonce.toString(),
82
82
  },
83
83
 
@@ -86,14 +86,14 @@ class SyncDepositEvents extends BaseTask {
86
86
  sourceChainId: sourceChainId.toString(),
87
87
  targetChainId: targetChainId.toString(),
88
88
  token: token,
89
- ammout: amount.toString(),
89
+ amount: amount.toString(),
90
90
  vnonce: vnonce.toString(),
91
91
  },
92
92
  status: "pending",
93
93
  })
94
94
 
95
95
  this.logger.info(
96
- `Execution queued: ${event.transactionHash} ${event.blockNumber}`
96
+ `Deposit queued: ${event.transactionHash} ${event.blockNumber}`
97
97
  );
98
98
  } catch (error) {
99
99
  this.logger.error(error);
@@ -113,11 +113,11 @@ class SyncDepositEvents extends BaseTask {
113
113
  getRpcProviderUrl(this.chainId)
114
114
  );
115
115
 
116
- this.contract = new ethers.Contract(
116
+ this.contract = getContract<InteropXGateway>(
117
117
  this.contractAddress,
118
118
  abi.interopXGateway,
119
119
  new ethers.Wallet(config.privateKey!, this.provider)
120
- ) as InteropXGateway;
120
+ );
121
121
 
122
122
  await super.start()
123
123
  }
@@ -1,11 +1,28 @@
1
1
  import { BaseTask } from "./BaseTask";
2
- import SyncInteropXGatewayDepositEvents from "./InteropXGateway/SyncDepositEvents";
2
+ import InteropXGatewayProcessDepositEvents from "./InteropXGateway/ProcessDepositEvents";
3
+ import InteropXGatewaySyncDepositEvents from "./InteropXGateway/SyncDepositEvents";
4
+
5
+ import InteropBridgeSyncWithdrawEvents from "./InteropBridge/SyncWithdrawEvents";
6
+ import InteropBridgeProcessWithdrawEvents from "./InteropBridge/ProcessWithdrawEvents";
3
7
 
4
8
  export class Tasks {
5
9
 
6
10
  tasks: BaseTask[] = [
7
- new SyncInteropXGatewayDepositEvents({
11
+ new InteropXGatewaySyncDepositEvents({
12
+ chainId: 43114
13
+ }),
14
+
15
+ new InteropXGatewayProcessDepositEvents({
8
16
  chainId: 43114
17
+ }),
18
+
19
+ new InteropBridgeSyncWithdrawEvents({
20
+ chainId: 137,
21
+ itokenAddress: '0xEab02fe1F016eE3e4106c1C6aad35FeEe657268E',
22
+ }),
23
+
24
+ new InteropBridgeProcessWithdrawEvents({
25
+ chainId: 137,
9
26
  })
10
27
  ];
11
28
 
@@ -10,7 +10,7 @@ import { encodeMulti, MetaTransaction, OperationType } from 'ethers-multisend';
10
10
  import { Transaction } from '@/db';
11
11
  import config from '@/config';
12
12
  import abi from '@/abi';
13
- import { InteropBridgeToken } from '@/typechain';
13
+ import { InteropBridgeToken, InteropXGateway } from '@/typechain';
14
14
 
15
15
  export const http = axios.create();
16
16
 
@@ -136,10 +136,21 @@ export const generateInteropTransactionHash = (data: { action: string, submitTra
136
136
  export const buildDataForTransaction = async (transaction: Transaction, type?: 'source' | 'target') => {
137
137
  type = type || transaction.sourceStatus === 'pending' ? 'source' : 'target';
138
138
 
139
+ switch (transaction.action) {
140
+ case "deposit":
141
+ return await buildDepositDataForTransaction(transaction, type);
142
+ case "withdraw":
143
+ return await buildWithdrawDataForTransaction(transaction, type);
144
+ default:
145
+ throw new Error(`Unknown action: ${transaction.action}`);
146
+ }
147
+ }
148
+
149
+ export const buildDepositDataForTransaction = async (transaction: Transaction, type: 'source' | 'target') => {
139
150
  const transactions: MetaTransaction[] = [];
140
151
 
141
- if(transaction.action != 'deposit') {
142
- throw new Error('Invalid action');
152
+ if (transaction.action !== 'deposit') {
153
+ throw new Error(`Invalid action: ${transaction.action}`)
143
154
  }
144
155
 
145
156
  if (transaction.action === 'deposit' && transaction.sourceStatus === 'pending') {
@@ -165,13 +176,13 @@ export const buildDataForTransaction = async (transaction: Transaction, type?: '
165
176
 
166
177
  const targetChainProvider = new ethers.providers.JsonRpcProvider(getRpcProviderUrl(transaction.targetChainId as ChainId));
167
178
  const targetWallet = new ethers.Wallet(config.privateKey, targetChainProvider);
168
- const interopBridgeContract = new ethers.Contract(itoken.address, abi.interopBridgeToken, targetWallet) as InteropBridgeToken;
179
+ const interopBridgeContract = getContract<InteropBridgeToken>(itoken.address, abi.interopBridgeToken, targetWallet);
169
180
 
170
181
  const { data } = await interopBridgeContract.populateTransaction.mint(
171
- transaction.submitEvent.to,
172
- transaction.submitEvent.amount,
173
- transaction.sourceChainId,
174
- transaction.sourceTransactionHash,
182
+ transaction.submitEvent.user,
183
+ ethers.BigNumber.from(transaction.submitEvent.amount.toString()),
184
+ ethers.BigNumber.from(transaction.submitEvent.sourceChainId.toString()),
185
+ transaction.submitTransactionHash,
175
186
  );
176
187
 
177
188
  transactions.push({
@@ -183,3 +194,108 @@ export const buildDataForTransaction = async (transaction: Transaction, type?: '
183
194
 
184
195
  return encodeMulti(transactions).data
185
196
  }
197
+
198
+ export const buildWithdrawDataForTransaction = async (transaction: Transaction, type: 'source' | 'target') => {
199
+ const transactions: MetaTransaction[] = [];
200
+
201
+ if (transaction.action !== 'withdraw') {
202
+ throw new Error(`Invalid action: ${transaction.action}`)
203
+ }
204
+
205
+ if (transaction.action === 'withdraw' && transaction.sourceStatus === 'pending') {
206
+ throw Error('Cannot build data for pending withdraw transaction');
207
+ }
208
+
209
+ if (!transaction.submitEvent) {
210
+ throw Error('Cannot build data for transaction without submitEvent');
211
+ }
212
+
213
+ const { to, amount, chainId, itoken: itokenAddress } = transaction.submitEvent;
214
+
215
+ const itoken = itokens[transaction.sourceChainId].find(token => token.address.toLowerCase() === itokenAddress.toLowerCase());
216
+
217
+ if (!itoken) {
218
+ throw Error('Cannot build data for transaction without itoken');
219
+ }
220
+
221
+ const token = tokens[chainId].find(t => t.symbol.toLowerCase() === itoken.symbol.toLowerCase());
222
+
223
+ if (!token) {
224
+ throw Error('Cannot build data for transaction without token');
225
+ }
226
+
227
+ const targetChainProvider = new ethers.providers.JsonRpcProvider(getRpcProviderUrl(transaction.targetChainId as ChainId));
228
+ const targetWallet = new ethers.Wallet(config.privateKey, targetChainProvider);
229
+ const gatewayAddress = addresses[chainId].interopXGateway;
230
+ const interopBridgeContract = getContract<InteropXGateway>(gatewayAddress, abi.interopXGateway, targetWallet);
231
+
232
+ const { data } = await interopBridgeContract.populateTransaction.systemWithdraw(
233
+ ethers.BigNumber.from(amount.toString()),
234
+ to,
235
+ token.address,
236
+ ethers.BigNumber.from(transaction.sourceChainId.toString()),
237
+ transaction.submitTransactionHash,
238
+ );
239
+
240
+ transactions.push({
241
+ to: gatewayAddress,
242
+ data: data!,
243
+ value: '0',
244
+ operation: OperationType.Call,
245
+ });
246
+
247
+ return encodeMulti(transactions).data
248
+ }
249
+
250
+
251
+ export function getContract<TContract extends ethers.Contract>(address: string, contractInterface: ethers.ContractInterface | any, signerOrProvider?: ethers.Signer | ethers.providers.Provider) {
252
+ if (!ethers.utils.getAddress(address) || address === ethers.constants.AddressZero) {
253
+ throw Error(`Invalid 'address' parameter '${address}'.`)
254
+ }
255
+
256
+ const contract = new ethers.Contract(
257
+ address,
258
+ contractInterface,
259
+ signerOrProvider
260
+ ) as TContract
261
+
262
+
263
+ return new Proxy(contract, {
264
+ get(target, prop, receiver) {
265
+ const value = Reflect.get(target, prop, receiver);
266
+
267
+ if (typeof value === 'function' && (contract.functions.hasOwnProperty(prop) || ['queryFilter'].includes(String(prop)))) {
268
+ return async (...args: any[]) => {
269
+ try {
270
+ return await value.bind(contract)(...args);
271
+ } catch (error) {
272
+ throw new Error(`Error calling "${String(prop)}" on "${address}": ${error.reason || error.message}`)
273
+ }
274
+ }
275
+ }
276
+
277
+
278
+ if (typeof value === 'object' && ['populateTransaction', 'estimateGas', 'functions', 'callStatic'].includes(String(prop))) {
279
+ const parentProp = String(prop);
280
+
281
+ return new Proxy(value, {
282
+ get(target, prop, receiver) {
283
+ const value = Reflect.get(target, prop, receiver);
284
+
285
+ if (typeof value === 'function') {
286
+ return async (...args: any[]) => {
287
+ try {
288
+ return await value.bind(contract)(...args);
289
+ } catch (error) {
290
+ throw new Error(`Error calling "${String(prop)}" using "${parentProp}" on "${address}": ${error.reason || error.message}`)
291
+ }
292
+ }
293
+ }
294
+ }
295
+ })
296
+ }
297
+
298
+ return value;
299
+ },
300
+ });
301
+ }