@instadapp/interop-x 0.0.0-dev.80722d6 → 0.0.0-dev.a168c79

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.
Files changed (52) hide show
  1. package/dist/package.json +69 -0
  2. package/dist/{abi → src/abi}/erc20.json +0 -0
  3. package/dist/{abi → src/abi}/gnosisSafe.json +0 -0
  4. package/dist/{abi → src/abi}/index.js +0 -0
  5. package/dist/{abi → src/abi}/interopBridgeToken.json +0 -0
  6. package/dist/{abi → src/abi}/interopXGateway.json +0 -0
  7. package/dist/src/api/index.js +33 -0
  8. package/dist/{config → src/config}/index.js +0 -0
  9. package/dist/{constants → src/constants}/addresses.js +0 -8
  10. package/dist/{constants → src/constants}/index.js +1 -0
  11. package/dist/src/constants/itokens.js +13 -0
  12. package/dist/{constants → src/constants}/tokens.js +0 -0
  13. package/dist/{db → src/db}/index.js +0 -0
  14. package/dist/{db → src/db}/models/index.js +0 -0
  15. package/dist/{db → src/db}/models/transaction.js +3 -1
  16. package/dist/{db → src/db}/sequelize.js +0 -0
  17. package/dist/{index.js → src/index.js} +5 -0
  18. package/dist/{logger → src/logger}/index.js +0 -0
  19. package/dist/{net → src/net}/index.js +0 -0
  20. package/dist/{net → src/net}/peer/index.js +0 -0
  21. package/dist/{net → src/net}/pool/index.js +0 -0
  22. package/dist/{net → src/net}/protocol/dial/BaseDialProtocol.js +0 -0
  23. package/dist/{net → src/net}/protocol/dial/SignatureDialProtocol.js +15 -12
  24. package/dist/{net → src/net}/protocol/index.js +0 -0
  25. package/dist/{tasks → src/tasks}/BaseTask.js +0 -0
  26. package/dist/src/tasks/InteropXGateway/ProcessDepositEvents.js +140 -0
  27. package/dist/{tasks → src/tasks}/InteropXGateway/SyncDepositEvents.js +13 -17
  28. package/dist/{tasks → src/tasks}/index.js +0 -0
  29. package/dist/{typechain → src/typechain}/Erc20.js +0 -0
  30. package/dist/{typechain → src/typechain}/GnosisSafe.js +0 -0
  31. package/dist/{typechain → src/typechain}/InteropBridgeToken.js +0 -0
  32. package/dist/{typechain → src/typechain}/InteropXGateway.js +0 -0
  33. package/dist/{typechain → src/typechain}/common.js +0 -0
  34. package/dist/{typechain → src/typechain}/factories/Erc20__factory.js +0 -0
  35. package/dist/{typechain → src/typechain}/factories/GnosisSafe__factory.js +0 -0
  36. package/dist/{typechain → src/typechain}/factories/InteropBridgeToken__factory.js +0 -0
  37. package/dist/{typechain → src/typechain}/factories/InteropXGateway__factory.js +0 -0
  38. package/dist/{typechain → src/typechain}/factories/index.js +0 -0
  39. package/dist/{typechain → src/typechain}/index.js +0 -0
  40. package/dist/{types.js → src/types.js} +0 -0
  41. package/dist/{utils → src/utils}/index.js +39 -3
  42. package/package.json +5 -2
  43. package/src/api/index.ts +33 -0
  44. package/src/constants/addresses.ts +0 -8
  45. package/src/constants/index.ts +1 -0
  46. package/src/constants/itokens.ts +10 -0
  47. package/src/db/models/transaction.ts +8 -4
  48. package/src/index.ts +7 -0
  49. package/src/net/protocol/dial/SignatureDialProtocol.ts +17 -13
  50. package/src/tasks/InteropXGateway/ProcessDepositEvents.ts +232 -0
  51. package/src/tasks/InteropXGateway/SyncDepositEvents.ts +18 -6
  52. package/src/utils/index.ts +61 -5
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@instadapp/interop-x",
3
+ "version": "0.0.0-dev.a168c79",
4
+ "license": "MIT",
5
+ "main": "dist/index.js",
6
+ "engines": {
7
+ "node": ">=16",
8
+ "yarn": "^1.22.0"
9
+ },
10
+ "scripts": {
11
+ "start": "yarn build && node bin/interop-x",
12
+ "build": "yarn generate-abi-types && export GIT_REF=$(git rev-parse --short HEAD) && rimraf ./dist && tsc -p tsconfig.json && replace-in-file '@GIT_SHORT_HASH@' $GIT_REF ./dist/**/*.js",
13
+ "dev": "yarn generate-abi-types && NODE_ENV=development nodemon",
14
+ "generate-abi-types": "typechain --target=ethers-v5 'src/abi/*.json' --out-dir 'src/typechain'",
15
+ "prepublishOnly": "yarn build"
16
+ },
17
+ "nodemonConfig": {
18
+ "watch": [
19
+ "src"
20
+ ],
21
+ "ext": "ts",
22
+ "exec": "./node_modules/.bin/ts-node --files -r tsconfig-paths/register ./src/index.ts"
23
+ },
24
+ "dependencies": {
25
+ "@achingbrain/libp2p-gossipsub": "^0.12.2",
26
+ "axios": "^0.27.1",
27
+ "axios-retry": "^3.2.4",
28
+ "bignumber.js": "^9.0.2",
29
+ "chalk": "4.1.2",
30
+ "dotenv": "^16.0.0",
31
+ "ethereumjs-util": "^7.1.4",
32
+ "ethers": "^5.6.4",
33
+ "ethers-multisend": "^2.1.1",
34
+ "expand-home-dir": "^0.0.3",
35
+ "fastify": "^3.28.0",
36
+ "fastify-cors": "^6.0.3",
37
+ "libp2p": "^0.36.2",
38
+ "libp2p-bootstrap": "^0.14.0",
39
+ "libp2p-kad-dht": "^0.28.6",
40
+ "libp2p-mdns": "^0.18.0",
41
+ "libp2p-mplex": "^0.10.7",
42
+ "libp2p-noise": "^4.0.0",
43
+ "libp2p-pubsub-peer-discovery": "^4.0.0",
44
+ "libp2p-tcp": "^0.17.2",
45
+ "libp2p-websockets": "^0.16.2",
46
+ "luxon": "^2.3.2",
47
+ "module-alias": "^2.2.2",
48
+ "sequelize": "^6.19.0",
49
+ "sqlite3": "^5.0.5",
50
+ "waait": "^1.0.5"
51
+ },
52
+ "bin": {
53
+ "interop-x": "bin/interop-x",
54
+ "interopx": "bin/interop-x"
55
+ },
56
+ "devDependencies": {
57
+ "@typechain/ethers-v5": "^10.0.0",
58
+ "@types/bn.js": "^5.1.0",
59
+ "@types/fs-extra": "^9.0.13",
60
+ "@types/node": "^17.0.17",
61
+ "nodemon": "^2.0.15",
62
+ "replace-in-file": "^6.3.2",
63
+ "rimraf": "^3.0.2",
64
+ "ts-node": "^10.5.0",
65
+ "tsconfig-paths": "^3.12.0",
66
+ "typechain": "^8.0.0",
67
+ "typescript": "^4.5.5"
68
+ }
69
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.startApiServer = void 0;
7
+ const fastify_1 = __importDefault(require("fastify"));
8
+ const fastify_cors_1 = __importDefault(require("fastify-cors"));
9
+ const logger_1 = __importDefault(require("@/logger"));
10
+ const db_1 = require("@/db");
11
+ const logger = new logger_1.default("RPC");
12
+ const server = (0, fastify_1.default)({ logger: false });
13
+ server.register(fastify_cors_1.default, {});
14
+ server.get('/', async () => 'Interop X API');
15
+ const startApiServer = async () => {
16
+ const HOST = process.env.API_HOST || '0.0.0.0';
17
+ const PORT = process.env.API_PORT || '8080';
18
+ try {
19
+ server.get('/transactions', async (req) => {
20
+ return await db_1.Transaction.findAndCountAll({
21
+ limit: 20,
22
+ offset: 0,
23
+ });
24
+ });
25
+ await server.listen(PORT, HOST);
26
+ logger.log(`RPC Server listening at http://${HOST}:${PORT}`);
27
+ }
28
+ catch (err) {
29
+ logger.error(err);
30
+ process.exit(1);
31
+ }
32
+ };
33
+ exports.startApiServer = startApiServer;
File without changes
@@ -6,23 +6,15 @@ exports.addresses = {
6
6
  gnosisSafe: '0x811Bff6eF88dAAA0aD6438386B534A81cE3F160F',
7
7
  multisend: '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',
8
8
  interopXGateway: '',
9
- interopBridgeTokens: [],
10
9
  },
11
10
  137: {
12
11
  gnosisSafe: '0x5635d2910e51da33d9DC0422c893CF4F28B69A25',
13
12
  multisend: '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',
14
13
  interopXGateway: '',
15
- interopBridgeTokens: [
16
- {
17
- address: '0x6c20F03598d5ABF729348E2868b0ff5e8A48aB1F',
18
- symbol: 'USDC',
19
- }
20
- ],
21
14
  },
22
15
  43114: {
23
16
  gnosisSafe: '0x31d7a5194Fe60AC209Cf1Ce2d539C9A60662Ed6b',
24
17
  multisend: '0x998739BFdAAdde7C933B942a68053933098f9EDa',
25
18
  interopXGateway: '0x8D27758751BA488690974B6Ccfcda771D462945f',
26
- interopBridgeTokens: [],
27
19
  }
28
20
  };
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./addresses"), exports);
18
18
  __exportStar(require("./tokens"), exports);
19
+ __exportStar(require("./itokens"), exports);
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.itokens = void 0;
4
+ exports.itokens = {
5
+ 1: [],
6
+ 137: [
7
+ {
8
+ address: '0x6c20F03598d5ABF729348E2868b0ff5e8A48aB1F',
9
+ symbol: 'USDC',
10
+ }
11
+ ],
12
+ 43114: []
13
+ };
File without changes
File without changes
File without changes
@@ -12,8 +12,10 @@ Transaction.init({
12
12
  autoIncrement: true,
13
13
  primaryKey: true
14
14
  },
15
+ submitTransactionHash: sequelize_2.DataTypes.NUMBER,
16
+ submitBlockNumber: sequelize_2.DataTypes.NUMBER,
15
17
  transactionHash: sequelize_2.DataTypes.STRING,
16
- type: sequelize_2.DataTypes.STRING,
18
+ action: sequelize_2.DataTypes.STRING,
17
19
  from: sequelize_2.DataTypes.STRING,
18
20
  to: sequelize_2.DataTypes.STRING,
19
21
  sourceChainId: sequelize_2.DataTypes.NUMBER,
File without changes
@@ -9,6 +9,7 @@ module_alias_1.default.addAliases({
9
9
  "@/logger": __dirname + "/logger",
10
10
  "@/tasks": __dirname + "/tasks",
11
11
  "@/utils": __dirname + "/utils",
12
+ "@/api": __dirname + "/api",
12
13
  "@/net": __dirname + "/net",
13
14
  "@/db": __dirname + "/db",
14
15
  "@/config": __dirname + "/config",
@@ -21,6 +22,7 @@ module_alias_1.default.addAliases({
21
22
  const assert_1 = __importDefault(require("assert"));
22
23
  const dotenv_1 = __importDefault(require("dotenv"));
23
24
  const ethers_1 = require("ethers");
25
+ const package_json_1 = __importDefault(require("../package.json"));
24
26
  dotenv_1.default.config();
25
27
  const logger_1 = __importDefault(require("@/logger"));
26
28
  const logger = new logger_1.default('Process');
@@ -38,12 +40,15 @@ catch (e) {
38
40
  logger.error('Invalid private key');
39
41
  process.exit(1);
40
42
  }
43
+ logger.debug(`Starting Interop X Node (v${package_json_1.default.version} - rev.a168c79)`);
41
44
  const tasks_1 = require("@/tasks");
42
45
  const net_1 = require("@/net");
46
+ const api_1 = require("@/api");
43
47
  async function main() {
44
48
  (0, net_1.startPeer)({});
45
49
  const tasks = new tasks_1.Tasks();
46
50
  tasks.start();
51
+ (0, api_1.startApiServer)();
47
52
  }
48
53
  main()
49
54
  .then(() => {
File without changes
File without changes
File without changes
File without changes
@@ -8,6 +8,8 @@ const BaseDialProtocol_1 = require("./BaseDialProtocol");
8
8
  const waait_1 = __importDefault(require("waait"));
9
9
  const config_1 = __importDefault(require("@/config"));
10
10
  const db_1 = require("@/db");
11
+ const utils_1 = require("@/utils");
12
+ const constants_1 = require("@/constants");
11
13
  class SignatureDialProtocol extends BaseDialProtocol_1.BaseDialProtocol {
12
14
  constructor(libp2p) {
13
15
  super(libp2p, '/interop-x/signatures');
@@ -15,31 +17,32 @@ class SignatureDialProtocol extends BaseDialProtocol_1.BaseDialProtocol {
15
17
  }
16
18
  async response(data) {
17
19
  const signer = config_1.default.wallet;
18
- let event;
20
+ let transaction;
19
21
  let maxTimeout = 20000;
20
22
  do {
21
- event = await db_1.Transaction.findOne({ where: { transactionHash: data.transactionHash } });
22
- if (!event) {
23
+ transaction = await db_1.Transaction.findOne({ where: { transactionHash: data.transactionHash } });
24
+ if (!transaction) {
23
25
  await (0, waait_1.default)(1000);
24
26
  maxTimeout -= 1000;
25
27
  }
26
- } while (!event && maxTimeout > 0);
27
- if (!event) {
28
+ } while (!transaction && maxTimeout > 0);
29
+ if (!transaction) {
28
30
  return {
29
31
  signer: signer.address,
30
32
  data: null,
31
33
  error: 'Event not found'
32
34
  };
33
35
  }
34
- // const signedData = await signGnosisSafeTx({
35
- // to: addresses[event.chainId as ChainId].multisend,
36
- // data: 'TODO',
37
- // chainId: event.chainId as ChainId,
38
- // safeTxGas: data.safeTxGas,
39
- // }, { signer });
36
+ const signedData = await (0, utils_1.signGnosisSafeTx)({
37
+ //TODO: chain id depends on event type
38
+ to: constants_1.addresses[transaction.sourceChainId].multisend,
39
+ data: (0, utils_1.buildDataForTransaction)(transaction, data.type),
40
+ chainId: transaction.sourceChainId,
41
+ safeTxGas: data.safeTxGas,
42
+ }, { signer });
40
43
  return {
41
44
  signer: signer.address,
42
- data: null, //signedData,
45
+ data: signedData
43
46
  };
44
47
  }
45
48
  }
File without changes
File without changes
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const BaseTask_1 = require("../BaseTask");
7
+ const logger_1 = __importDefault(require("@/logger"));
8
+ const ethers_1 = require("ethers");
9
+ const abi_1 = __importDefault(require("@/abi"));
10
+ const db_1 = require("@/db");
11
+ const utils_1 = require("@/utils");
12
+ const constants_1 = require("@/constants");
13
+ const config_1 = __importDefault(require("@/config"));
14
+ const sequelize_1 = require("sequelize");
15
+ const waait_1 = __importDefault(require("waait"));
16
+ const net_1 = require("@/net");
17
+ const generateGnosisTransaction = async (transactionData, safeContract) => {
18
+ let isExecuted = await safeContract.dataHashes(await safeContract.getTransactionHash(transactionData.to, transactionData.value, transactionData.data, transactionData.operation, transactionData.safeTxGas, transactionData.baseGas, transactionData.gasPrice, transactionData.gasToken, transactionData.refundReceiver, transactionData.nonce));
19
+ while (isExecuted == 1) {
20
+ transactionData.safeTxGas = ethers_1.BigNumber.from(String(transactionData.safeTxGas)).add(1).toString();
21
+ isExecuted = await safeContract.dataHashes(await safeContract.getTransactionHash(transactionData.to, transactionData.value, transactionData.data, transactionData.operation, transactionData.safeTxGas, transactionData.baseGas, transactionData.gasPrice, transactionData.gasToken, transactionData.refundReceiver, transactionData.nonce));
22
+ }
23
+ return transactionData;
24
+ };
25
+ class ProcessDepositEvents extends BaseTask_1.BaseTask {
26
+ constructor({ chainId }) {
27
+ super({
28
+ logger: new logger_1.default("InteropXGateway::ProcessDepositEvents"),
29
+ });
30
+ this.leadNodeOnly = true;
31
+ this.chainId = chainId;
32
+ }
33
+ async pollHandler() {
34
+ var _a;
35
+ const blockNumber = await this.provider.getBlockNumber();
36
+ const transaction = await db_1.Transaction.findOne({
37
+ where: {
38
+ status: 'pending',
39
+ sourceStatus: 'success',
40
+ action: 'deposit',
41
+ sourceCreatedAt: {
42
+ [sequelize_1.Op.gte]: new Date(Date.now() - 12 * 60 * 60 * 1000),
43
+ },
44
+ sourceBlockNumber: {
45
+ [sequelize_1.Op.lt]: blockNumber - 12,
46
+ }
47
+ }
48
+ });
49
+ if (!transaction) {
50
+ return;
51
+ }
52
+ transaction.sourceStatus = 'pending';
53
+ await transaction.save();
54
+ // refresh event data?
55
+ const targetChainProvider = new ethers_1.ethers.providers.JsonRpcProvider((0, utils_1.getRpcProviderUrl)(transaction.targetChainId));
56
+ const targetWallet = new ethers_1.ethers.Wallet(config_1.default.privateKey, targetChainProvider);
57
+ const safeAddress = constants_1.addresses[transaction.targetChainId].gnosisSafe;
58
+ const safeContract = new ethers_1.ethers.Contract(safeAddress, abi_1.default.gnosisSafe, targetWallet);
59
+ const ownersThreshold = await safeContract.getThreshold();
60
+ await (0, waait_1.default)(10000);
61
+ let gnosisTx = await generateGnosisTransaction({
62
+ baseGas: "0",
63
+ data: (0, utils_1.buildDataForTransaction)(transaction),
64
+ gasPrice: "0",
65
+ gasToken: "0x0000000000000000000000000000000000000000",
66
+ nonce: '0',
67
+ operation: "1",
68
+ refundReceiver: "0x0000000000000000000000000000000000000000",
69
+ safeAddress: safeAddress,
70
+ safeTxGas: "79668",
71
+ to: constants_1.addresses[transaction.targetChainId].multisend,
72
+ value: "0",
73
+ }, safeContract);
74
+ const owners = await safeContract.getOwners().then(owners => owners.map(owner => owner.toLowerCase()));
75
+ const ownerPeerIds = net_1.peerPool.activePeers.filter(peer => owners.includes(peer.publicAddress.toLowerCase())).map(peer => peer.id);
76
+ console.log(`Collecting signatures for execution ${transaction.transactionHash}`);
77
+ const signatures = await net_1.protocol.requestSignatures({
78
+ type: 'source',
79
+ transactionHash: transaction.transactionHash,
80
+ safeTxGas: gnosisTx.safeTxGas,
81
+ safeNonce: gnosisTx.nonce
82
+ }, ownerPeerIds);
83
+ const validSignatures = signatures.filter(s => !!s.data && s.data !== '0x');
84
+ console.log({ signatures, validSignatures, ownersThreshold: ownersThreshold.toString() });
85
+ if (validSignatures.length === 0 || ownersThreshold.gt(validSignatures.length)) {
86
+ await transaction.save();
87
+ transaction.sourceDelayUntil = new Date(Date.now() + 30 * 1000);
88
+ transaction.sourceStatus = 'pending';
89
+ await transaction.save();
90
+ const errorMessage = (_a = signatures.find(s => !!s.error)) === null || _a === void 0 ? void 0 : _a.error;
91
+ throw new Error(`Not enough signatures` + (errorMessage ? `: ${errorMessage}` : ''));
92
+ }
93
+ const execTransactionParams = [
94
+ gnosisTx.to,
95
+ gnosisTx.value,
96
+ gnosisTx.data,
97
+ gnosisTx.operation,
98
+ gnosisTx.safeTxGas,
99
+ gnosisTx.baseGas,
100
+ gnosisTx.gasPrice,
101
+ gnosisTx.gasToken,
102
+ gnosisTx.refundReceiver,
103
+ (0, utils_1.buildSignatureBytes)(validSignatures),
104
+ ];
105
+ console.log(`Executing transaction for execution ${transaction.transactionHash}`);
106
+ console.log({
107
+ execTransactionParams
108
+ });
109
+ const { data: txData } = await safeContract.populateTransaction.execTransaction(gnosisTx.to, gnosisTx.value, gnosisTx.data, gnosisTx.operation, gnosisTx.safeTxGas, gnosisTx.baseGas, gnosisTx.gasPrice, gnosisTx.gasToken, gnosisTx.refundReceiver, (0, utils_1.buildSignatureBytes)(validSignatures));
110
+ const txSent = await targetWallet.sendTransaction({
111
+ from: targetWallet.address,
112
+ gasPrice: ethers_1.BigNumber.from(120 * 10 ** 9),
113
+ gasLimit: ethers_1.BigNumber.from(6000000),
114
+ to: safeAddress,
115
+ data: txData,
116
+ });
117
+ const receipt = await txSent.wait();
118
+ const parsedLogs = [];
119
+ receipt.logs.forEach((log) => {
120
+ try {
121
+ parsedLogs.push(safeContract.interface.parseLog(log));
122
+ }
123
+ catch (e) { }
124
+ });
125
+ if (parsedLogs.find(e => e.name === 'ExecutionSuccess')) {
126
+ console.log('ExecutionSuccess');
127
+ }
128
+ else {
129
+ console.log('ExecutionFailure');
130
+ }
131
+ }
132
+ async start() {
133
+ this.logger.info(`Starting execution watcher on interop chain`);
134
+ this.contractAddress = constants_1.addresses[this.chainId].interopXGateway;
135
+ this.provider = new ethers_1.ethers.providers.JsonRpcProvider((0, utils_1.getRpcProviderUrl)(this.chainId));
136
+ this.contract = new ethers_1.ethers.Contract(this.contractAddress, abi_1.default.interopXGateway, new ethers_1.ethers.Wallet(config_1.default.privateKey, this.provider));
137
+ await super.start();
138
+ }
139
+ }
140
+ exports.default = ProcessDepositEvents;
@@ -29,8 +29,8 @@ class SyncDepositEvents extends BaseTask_1.BaseTask {
29
29
  }
30
30
  const { sourceChainId, targetChainId, user, vnonce, amount, token } = event.args;
31
31
  const uniqueIdentifier = {
32
- type: 'desposit',
33
- sourceTransactionHash: event.transactionHash,
32
+ action: 'deposit',
33
+ submitTransactionHash: event.transactionHash,
34
34
  sourceChainId: sourceChainId.toNumber(),
35
35
  targetChainId: targetChainId.toNumber(),
36
36
  };
@@ -38,27 +38,23 @@ class SyncDepositEvents extends BaseTask_1.BaseTask {
38
38
  continue;
39
39
  }
40
40
  const tx = await event.getTransaction();
41
- await db_1.Transaction.create({
42
- transactionHash: (0, utils_1.generateInteropTransactionHash)(uniqueIdentifier),
43
- type: 'deposit',
44
- from: tx.from,
45
- to: user,
46
- sourceChainId: sourceChainId.toNumber(),
47
- sourceTransactionHash: event.transactionHash,
48
- sourceBlockNumber: event.blockNumber,
49
- sourceStatus: "uninitialised",
50
- targetChainId: targetChainId.toNumber(),
51
- targetStatus: "uninitialised",
52
- submitEvent: {
41
+ await db_1.Transaction.create(Object.assign(Object.assign({}, uniqueIdentifier), { transactionHash: (0, utils_1.generateInteropTransactionHash)(uniqueIdentifier), from: tx.from, to: user, submitTransactionHash: event.transactionHash, submitBlockNumber: event.blockNumber,
42
+ // submit & source are the same
43
+ sourceTransactionHash: event.transactionHash, sourceBlockNumber: event.blockNumber, sourceStatus: "success", targetStatus: "uninitialised", submitEvent: {
44
+ user,
45
+ sourceChainId: sourceChainId.toString(),
46
+ targetChainId: targetChainId.toString(),
47
+ token: token,
48
+ ammout: amount.toString(),
49
+ vnonce: vnonce.toString(),
50
+ }, sourceEvent: {
53
51
  user,
54
52
  sourceChainId: sourceChainId.toString(),
55
53
  targetChainId: targetChainId.toString(),
56
54
  token: token,
57
55
  ammout: amount.toString(),
58
56
  vnonce: vnonce.toString(),
59
- },
60
- status: "pending",
61
- });
57
+ }, status: "pending" }));
62
58
  this.logger.info(`Execution queued: ${event.transactionHash} ${event.blockNumber}`);
63
59
  }
64
60
  catch (error) {
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.generateInteropTransactionHash = exports.asyncCallWithTimeout = exports.buildSignatureBytes = exports.getRpcProviderUrl = exports.signGnosisSafeTx = exports.short = exports.http = void 0;
6
+ exports.buildDataForTransaction = exports.generateInteropTransactionHash = exports.asyncCallWithTimeout = exports.buildSignatureBytes = exports.getRpcProviderUrl = exports.signGnosisSafeTx = exports.short = exports.http = void 0;
7
7
  /**
8
8
  * @module util
9
9
  */
@@ -11,6 +11,9 @@ const axios_1 = __importDefault(require("axios"));
11
11
  const axios_retry_1 = __importDefault(require("axios-retry"));
12
12
  const constants_1 = require("@/constants");
13
13
  const ethers_1 = require("ethers");
14
+ const ethers_multisend_1 = require("ethers-multisend");
15
+ const config_1 = __importDefault(require("@/config"));
16
+ const abi_1 = __importDefault(require("@/abi"));
14
17
  exports.http = axios_1.default.create();
15
18
  (0, axios_retry_1.default)(exports.http, { retries: 3, retryDelay: axios_retry_1.default.exponentialDelay });
16
19
  function short(buffer) {
@@ -92,10 +95,43 @@ const asyncCallWithTimeout = async (asyncPromise, timeout) => {
92
95
  exports.asyncCallWithTimeout = asyncCallWithTimeout;
93
96
  const generateInteropTransactionHash = (data) => {
94
97
  return ethers_1.ethers.utils.solidityKeccak256(['string', 'string', 'string', 'string'], [
95
- String(data.type),
96
- String(data.sourceTransactionHash),
98
+ String(data.action),
99
+ String(data.submitTransactionHash),
97
100
  String(data.sourceChainId),
98
101
  String(data.targetChainId),
99
102
  ]);
100
103
  };
101
104
  exports.generateInteropTransactionHash = generateInteropTransactionHash;
105
+ const buildDataForTransaction = async (transaction, type) => {
106
+ type = type || transaction.sourceStatus === 'pending' ? 'source' : 'target';
107
+ const transactions = [];
108
+ if (transaction.action != 'deposit') {
109
+ throw new Error('Invalid action');
110
+ }
111
+ if (transaction.action === 'deposit' && transaction.sourceStatus === 'pending') {
112
+ throw Error('Cannot build data for pending deposit transaction');
113
+ }
114
+ if (!transaction.submitEvent) {
115
+ throw Error('Cannot build data for transaction without submitEvent');
116
+ }
117
+ const token = constants_1.tokens[transaction.sourceChainId].find(token => token.address.toLowerCase() === transaction.submitEvent.token.toLowerCase());
118
+ if (!token) {
119
+ throw Error('Cannot build data for transaction without token');
120
+ }
121
+ const itoken = constants_1.itokens[transaction.targetChainId].find(itoken => itoken.symbol.toLowerCase() === token.symbol.toLowerCase());
122
+ if (!itoken) {
123
+ throw Error('Cannot build data for transaction without itoken');
124
+ }
125
+ const targetChainProvider = new ethers_1.ethers.providers.JsonRpcProvider((0, exports.getRpcProviderUrl)(transaction.targetChainId));
126
+ const targetWallet = new ethers_1.ethers.Wallet(config_1.default.privateKey, targetChainProvider);
127
+ const interopBridgeContract = new ethers_1.ethers.Contract(itoken.address, abi_1.default.interopBridgeToken, targetWallet);
128
+ const { data } = await interopBridgeContract.populateTransaction.mint(transaction.submitEvent.to, transaction.submitEvent.amount, transaction.sourceChainId, transaction.sourceTransactionHash);
129
+ transactions.push({
130
+ to: itoken.address,
131
+ data: data,
132
+ value: '0',
133
+ operation: ethers_multisend_1.OperationType.Call,
134
+ });
135
+ return (0, ethers_multisend_1.encodeMulti)(transactions).data;
136
+ };
137
+ exports.buildDataForTransaction = buildDataForTransaction;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instadapp/interop-x",
3
- "version": "0.0.0-dev.80722d6",
3
+ "version": "0.0.0-dev.a168c79",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "engines": {
@@ -32,6 +32,8 @@
32
32
  "ethers": "^5.6.4",
33
33
  "ethers-multisend": "^2.1.1",
34
34
  "expand-home-dir": "^0.0.3",
35
+ "fastify": "^3.28.0",
36
+ "fastify-cors": "^6.0.3",
35
37
  "libp2p": "^0.36.2",
36
38
  "libp2p-bootstrap": "^0.14.0",
37
39
  "libp2p-kad-dht": "^0.28.6",
@@ -48,7 +50,8 @@
48
50
  "waait": "^1.0.5"
49
51
  },
50
52
  "bin": {
51
- "interop-x": "bin/interop-x"
53
+ "interop-x": "bin/interop-x",
54
+ "interopx": "bin/interop-x"
52
55
  },
53
56
  "devDependencies": {
54
57
  "@typechain/ethers-v5": "^10.0.0",
@@ -0,0 +1,33 @@
1
+ import fastify from "fastify"
2
+ import cors from 'fastify-cors'
3
+ import Logger from "@/logger"
4
+ import { Transaction } from "@/db";
5
+
6
+ const logger = new Logger("RPC");
7
+
8
+
9
+ const server = fastify({ logger: false })
10
+
11
+ server.register(cors, {})
12
+
13
+ server.get('/', async () => 'Interop X API')
14
+
15
+ export const startApiServer = async () => {
16
+ const HOST = process.env.API_HOST || '0.0.0.0';
17
+ const PORT = process.env.API_PORT || '8080';
18
+ try {
19
+ server.get('/transactions', async (req) => {
20
+ return await Transaction.findAndCountAll({
21
+ limit: 20,
22
+ offset: 0,
23
+ })
24
+ })
25
+
26
+ await server.listen(PORT, HOST)
27
+
28
+ logger.log(`RPC Server listening at http://${HOST}:${PORT}`)
29
+ } catch (err) {
30
+ logger.error(err)
31
+ process.exit(1)
32
+ }
33
+ }
@@ -3,23 +3,15 @@ export const addresses = {
3
3
  gnosisSafe: '0x811Bff6eF88dAAA0aD6438386B534A81cE3F160F',
4
4
  multisend: '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',
5
5
  interopXGateway: '',
6
- interopBridgeTokens: [],
7
6
  },
8
7
  137: {
9
8
  gnosisSafe: '0x5635d2910e51da33d9DC0422c893CF4F28B69A25',
10
9
  multisend: '0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761',
11
10
  interopXGateway: '',
12
- interopBridgeTokens: [
13
- {
14
- address: '0x6c20F03598d5ABF729348E2868b0ff5e8A48aB1F',
15
- symbol : 'USDC',
16
- }
17
- ],
18
11
  },
19
12
  43114: {
20
13
  gnosisSafe: '0x31d7a5194Fe60AC209Cf1Ce2d539C9A60662Ed6b',
21
14
  multisend: '0x998739BFdAAdde7C933B942a68053933098f9EDa',
22
15
  interopXGateway: '0x8D27758751BA488690974B6Ccfcda771D462945f',
23
- interopBridgeTokens: [],
24
16
  }
25
17
  }
@@ -1,2 +1,3 @@
1
1
  export * from './addresses';
2
2
  export * from './tokens';
3
+ export * from './itokens';
@@ -0,0 +1,10 @@
1
+ export const itokens = {
2
+ 1: [],
3
+ 137: [
4
+ {
5
+ address: '0x6c20F03598d5ABF729348E2868b0ff5e8A48aB1F',
6
+ symbol: 'USDC',
7
+ }
8
+ ],
9
+ 43114: []
10
+ };
@@ -5,14 +5,16 @@ export class Transaction extends Model<InferAttributes<Transaction>, InferCreati
5
5
  declare id: CreationOptional<number>;
6
6
 
7
7
  declare transactionHash: string;
8
- declare type: string;
8
+ declare action: string;
9
9
  declare from: string;
10
10
  declare to: string;
11
11
 
12
+ declare submitTransactionHash: string;
13
+ declare submitBlockNumber: number;
12
14
 
13
15
  declare sourceChainId: number;
14
- declare sourceTransactionHash: string;
15
- declare sourceBlockNumber: number;
16
+ declare sourceTransactionHash: CreationOptional<string>;
17
+ declare sourceBlockNumber: CreationOptional<number>;
16
18
  declare sourceStatus: string;
17
19
  declare sourceErrors: CreationOptional<string[]>;
18
20
  declare sourceCreatedAt: CreationOptional<Date>;
@@ -45,9 +47,11 @@ Transaction.init({
45
47
  primaryKey: true
46
48
  },
47
49
 
50
+ submitTransactionHash: DataTypes.NUMBER,
51
+ submitBlockNumber: DataTypes.NUMBER,
48
52
 
49
53
  transactionHash: DataTypes.STRING,
50
- type: DataTypes.STRING,
54
+ action: DataTypes.STRING,
51
55
 
52
56
  from: DataTypes.STRING,
53
57
  to: DataTypes.STRING,
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ moduleAlias.addAliases({
5
5
  "@/logger": __dirname + "/logger",
6
6
  "@/tasks": __dirname + "/tasks",
7
7
  "@/utils": __dirname + "/utils",
8
+ "@/api": __dirname + "/api",
8
9
  "@/net": __dirname + "/net",
9
10
  "@/db": __dirname + "/db",
10
11
  "@/config": __dirname + "/config",
@@ -18,6 +19,7 @@ moduleAlias();
18
19
  import assert from "assert";
19
20
  import dotenv from "dotenv";
20
21
  import { ethers } from "ethers";
22
+ import packageJson from '../package.json'
21
23
  dotenv.config();
22
24
 
23
25
  import Logger from "@/logger";
@@ -40,8 +42,11 @@ try {
40
42
  process.exit(1)
41
43
  }
42
44
 
45
+ logger.debug(`Starting Interop X Node (v${packageJson.version} - rev.@GIT_SHORT_HASH@)`)
46
+
43
47
  import { Tasks } from "@/tasks";
44
48
  import { startPeer } from "@/net";
49
+ import { startApiServer } from '@/api';
45
50
 
46
51
  async function main() {
47
52
 
@@ -50,6 +55,8 @@ async function main() {
50
55
  const tasks = new Tasks()
51
56
 
52
57
  tasks.start();
58
+
59
+ startApiServer()
53
60
  }
54
61
 
55
62
  main()
@@ -2,9 +2,12 @@ import { BaseDialProtocol } from "./BaseDialProtocol";
2
2
  import wait from "waait";
3
3
  import config from "@/config";
4
4
  import { Transaction } from "@/db";
5
+ import { buildDataForTransaction, signGnosisSafeTx } from "@/utils";
6
+ import { addresses } from "@/constants";
7
+ import { ChainId } from "@/types";
5
8
 
6
9
  export interface ISignatureRequest {
7
- type: string,
10
+ type: 'source' | 'target' ,
8
11
  transactionHash: string
9
12
  safeTxGas: string
10
13
  safeNonce: string
@@ -24,19 +27,19 @@ export class SignatureDialProtocol extends BaseDialProtocol<ISignatureRequest, I
24
27
  async response(data: ISignatureRequest): Promise<ISignatureResponse> {
25
28
  const signer = config.wallet;
26
29
 
27
- let event: Transaction | null;
30
+ let transaction: Transaction | null;
28
31
  let maxTimeout = 20000;
29
32
 
30
33
  do {
31
- event = await Transaction.findOne({ where: { transactionHash: data.transactionHash } })
34
+ transaction = await Transaction.findOne({ where: { transactionHash: data.transactionHash } })
32
35
 
33
- if (!event) {
36
+ if (!transaction) {
34
37
  await wait(1000);
35
38
  maxTimeout -= 1000;
36
39
  }
37
- } while (!event && maxTimeout > 0)
40
+ } while (!transaction && maxTimeout > 0)
38
41
 
39
- if (!event) {
42
+ if (!transaction) {
40
43
  return {
41
44
  signer: signer.address,
42
45
  data: null,
@@ -44,16 +47,17 @@ export class SignatureDialProtocol extends BaseDialProtocol<ISignatureRequest, I
44
47
  };
45
48
  }
46
49
 
47
- // const signedData = await signGnosisSafeTx({
48
- // to: addresses[event.chainId as ChainId].multisend,
49
- // data: 'TODO',
50
- // chainId: event.chainId as ChainId,
51
- // safeTxGas: data.safeTxGas,
52
- // }, { signer });
50
+ const signedData = await signGnosisSafeTx({
51
+ //TODO: chain id depends on event type
52
+ to: addresses[transaction.sourceChainId].multisend,
53
+ data: buildDataForTransaction(transaction, data.type),
54
+ chainId: transaction.sourceChainId as ChainId,
55
+ safeTxGas: data.safeTxGas,
56
+ }, { signer });
53
57
 
54
58
  return {
55
59
  signer: signer.address,
56
- data: null, //signedData,
60
+ data: signedData
57
61
  }
58
62
  }
59
63
  }
@@ -0,0 +1,232 @@
1
+ import { BaseTask } from "../BaseTask";
2
+ import Logger from '@/logger';
3
+ import { BigNumber, ethers } from "ethers";
4
+ import abi from "@/abi";
5
+ import { Transaction } from "@/db";
6
+ import { buildDataForTransaction, buildSignatureBytes, getRpcProviderUrl, Signature } from "@/utils";
7
+ import { addresses } from "@/constants";
8
+ import { ChainId } from "@/types";
9
+ import config from "@/config";
10
+ import { GnosisSafe, InteropXGateway } from "@/typechain";
11
+ import { Op } from "sequelize";
12
+ import wait from "waait";
13
+ import { peerPool, protocol } from "@/net";
14
+ import { LogDescription } from "ethers/lib/utils";
15
+
16
+ const generateGnosisTransaction = async (transactionData: any, safeContract: any) => {
17
+ let isExecuted = await safeContract.dataHashes(
18
+ await safeContract.getTransactionHash(
19
+ transactionData.to,
20
+ transactionData.value,
21
+ transactionData.data,
22
+ transactionData.operation,
23
+ transactionData.safeTxGas,
24
+ transactionData.baseGas,
25
+ transactionData.gasPrice,
26
+ transactionData.gasToken,
27
+ transactionData.refundReceiver,
28
+ transactionData.nonce
29
+ )
30
+ )
31
+
32
+ while (isExecuted == 1) {
33
+ transactionData.safeTxGas = BigNumber.from(String(transactionData.safeTxGas)).add(1).toString()
34
+
35
+ isExecuted = await safeContract.dataHashes(
36
+ await safeContract.getTransactionHash(
37
+ transactionData.to,
38
+ transactionData.value,
39
+ transactionData.data,
40
+ transactionData.operation,
41
+ transactionData.safeTxGas,
42
+ transactionData.baseGas,
43
+ transactionData.gasPrice,
44
+ transactionData.gasToken,
45
+ transactionData.refundReceiver,
46
+ transactionData.nonce
47
+ )
48
+ )
49
+ }
50
+
51
+ return transactionData
52
+ }
53
+
54
+ class ProcessDepositEvents extends BaseTask {
55
+ contractAddress: string;
56
+ provider: ethers.providers.JsonRpcProvider;
57
+ contract: InteropXGateway;
58
+ chainId: ChainId;
59
+ leadNodeOnly = true
60
+
61
+ constructor({ chainId }: { chainId: ChainId }) {
62
+ super({
63
+ logger: new Logger("InteropXGateway::ProcessDepositEvents"),
64
+ })
65
+ this.chainId = chainId;
66
+ }
67
+
68
+ async pollHandler() {
69
+ const blockNumber = await this.provider.getBlockNumber()
70
+
71
+ const transaction = await Transaction.findOne({
72
+ where: {
73
+ status: 'pending',
74
+ sourceStatus: 'success',
75
+ action: 'deposit',
76
+ sourceCreatedAt: {
77
+ [Op.gte]: new Date(Date.now() - 12 * 60 * 60 * 1000),
78
+ },
79
+ sourceBlockNumber: {
80
+ [Op.lt]: blockNumber - 12,
81
+ }
82
+ }
83
+ })
84
+
85
+ if (!transaction) {
86
+ return;
87
+ }
88
+
89
+
90
+ transaction.sourceStatus = 'pending';
91
+ await transaction.save();
92
+
93
+
94
+ // refresh event data?
95
+
96
+ const targetChainProvider = new ethers.providers.JsonRpcProvider(
97
+ getRpcProviderUrl(transaction.targetChainId as ChainId)
98
+ );
99
+
100
+ const targetWallet = new ethers.Wallet(config.privateKey!, targetChainProvider);
101
+
102
+ const safeAddress = addresses[transaction.targetChainId].gnosisSafe;
103
+
104
+ const safeContract = new ethers.Contract(
105
+ safeAddress,
106
+ abi.gnosisSafe,
107
+ targetWallet
108
+ ) as GnosisSafe;
109
+
110
+ const ownersThreshold = await safeContract.getThreshold();
111
+
112
+ await wait(10000);
113
+
114
+ let gnosisTx = await generateGnosisTransaction({
115
+ baseGas: "0",
116
+ data: buildDataForTransaction(transaction),
117
+ gasPrice: "0",
118
+ gasToken: "0x0000000000000000000000000000000000000000",
119
+ nonce: '0',
120
+ operation: "1",
121
+ refundReceiver: "0x0000000000000000000000000000000000000000",
122
+ safeAddress: safeAddress,
123
+ safeTxGas: "79668",
124
+ to: addresses[transaction.targetChainId].multisend,
125
+ value: "0",
126
+ }, safeContract);
127
+
128
+ const owners = await safeContract.getOwners().then(owners => owners.map(owner => owner.toLowerCase()));
129
+
130
+ const ownerPeerIds = peerPool.activePeers.filter(peer => owners.includes(peer.publicAddress.toLowerCase())).map(peer => peer.id)
131
+
132
+ console.log(`Collecting signatures for execution ${transaction.transactionHash}`)
133
+
134
+ const signatures = await protocol.requestSignatures({
135
+ type: 'source',
136
+ transactionHash: transaction.transactionHash,
137
+ safeTxGas: gnosisTx.safeTxGas,
138
+ safeNonce: gnosisTx.nonce
139
+ }, ownerPeerIds)
140
+
141
+
142
+ const validSignatures = signatures.filter(s => !!s.data && s.data !== '0x') as Signature[];
143
+
144
+ console.log({ signatures, validSignatures, ownersThreshold: ownersThreshold.toString() });
145
+
146
+ if (validSignatures.length === 0 || ownersThreshold.gt(validSignatures.length)) {
147
+ await transaction.save();
148
+ transaction.sourceDelayUntil = new Date(Date.now() + 30 * 1000);
149
+ transaction.sourceStatus = 'pending'
150
+
151
+ await transaction.save();
152
+ const errorMessage = signatures.find(s => !!s.error)?.error;
153
+ throw new Error(`Not enough signatures` + (errorMessage ? `: ${errorMessage}` : ''));
154
+ }
155
+
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
+
169
+ console.log(`Executing transaction for execution ${transaction.transactionHash}`)
170
+
171
+ console.log({
172
+ execTransactionParams
173
+ })
174
+
175
+ const { data: txData } = await safeContract.populateTransaction.execTransaction(
176
+ gnosisTx.to,
177
+ gnosisTx.value,
178
+ gnosisTx.data,
179
+ gnosisTx.operation,
180
+ gnosisTx.safeTxGas,
181
+ gnosisTx.baseGas,
182
+ gnosisTx.gasPrice,
183
+ gnosisTx.gasToken,
184
+ gnosisTx.refundReceiver,
185
+ buildSignatureBytes(validSignatures)
186
+ );
187
+
188
+ const txSent = await targetWallet.sendTransaction({
189
+ from: targetWallet.address,
190
+ gasPrice: BigNumber.from(120 * 10 ** 9),
191
+ gasLimit: BigNumber.from(6_000_000),
192
+ to: safeAddress,
193
+ data: txData,
194
+ })
195
+
196
+ const receipt = await txSent.wait();
197
+
198
+ const parsedLogs: LogDescription[] = [];
199
+
200
+ receipt.logs.forEach((log) => {
201
+ try {
202
+ parsedLogs.push(safeContract.interface.parseLog(log));
203
+ } catch (e) { }
204
+ });
205
+
206
+ if (parsedLogs.find(e => e.name === 'ExecutionSuccess')) {
207
+ console.log('ExecutionSuccess')
208
+ } else {
209
+ console.log('ExecutionFailure')
210
+ }
211
+ }
212
+
213
+ async start(): Promise<void> {
214
+ this.logger.info(`Starting execution watcher on interop chain`);
215
+
216
+ this.contractAddress = addresses[this.chainId].interopXGateway;
217
+
218
+ this.provider = new ethers.providers.JsonRpcProvider(
219
+ getRpcProviderUrl(this.chainId)
220
+ );
221
+
222
+ this.contract = new ethers.Contract(
223
+ this.contractAddress,
224
+ abi.interopXGateway,
225
+ new ethers.Wallet(config.privateKey!, this.provider)
226
+ ) as InteropXGateway;
227
+
228
+ await super.start()
229
+ }
230
+ }
231
+
232
+ export default ProcessDepositEvents;
@@ -43,8 +43,8 @@ class SyncDepositEvents extends BaseTask {
43
43
  const { sourceChainId, targetChainId, user, vnonce, amount, token } = event.args;
44
44
 
45
45
  const uniqueIdentifier = {
46
- type: 'desposit',
47
- sourceTransactionHash: event.transactionHash,
46
+ action: 'deposit',
47
+ submitTransactionHash: event.transactionHash,
48
48
  sourceChainId: sourceChainId.toNumber(),
49
49
  targetChainId: targetChainId.toNumber(),
50
50
  }
@@ -56,17 +56,20 @@ class SyncDepositEvents extends BaseTask {
56
56
  const tx = await event.getTransaction()
57
57
 
58
58
  await Transaction.create({
59
+ ...uniqueIdentifier,
59
60
  transactionHash: generateInteropTransactionHash(uniqueIdentifier),
60
- type: 'deposit',
61
61
  from: tx.from,
62
62
  to: user,
63
63
 
64
- sourceChainId: sourceChainId.toNumber(),
64
+
65
+ submitTransactionHash: event.transactionHash,
66
+ submitBlockNumber: event.blockNumber,
67
+
68
+ // submit & source are the same
65
69
  sourceTransactionHash: event.transactionHash,
66
70
  sourceBlockNumber: event.blockNumber,
67
- sourceStatus: "uninitialised",
71
+ sourceStatus: "success",
68
72
 
69
- targetChainId: targetChainId.toNumber(),
70
73
  targetStatus: "uninitialised",
71
74
 
72
75
  submitEvent: {
@@ -77,6 +80,15 @@ class SyncDepositEvents extends BaseTask {
77
80
  ammout: amount.toString(),
78
81
  vnonce: vnonce.toString(),
79
82
  },
83
+
84
+ sourceEvent: {
85
+ user,
86
+ sourceChainId: sourceChainId.toString(),
87
+ targetChainId: targetChainId.toString(),
88
+ token: token,
89
+ ammout: amount.toString(),
90
+ vnonce: vnonce.toString(),
91
+ },
80
92
  status: "pending",
81
93
  })
82
94
 
@@ -3,9 +3,14 @@
3
3
  */
4
4
  import axios from 'axios'
5
5
  import axiosRetry from "axios-retry";
6
- import { addresses } from '@/constants';
6
+ import { addresses, itokens, tokens } from '@/constants';
7
7
  import { ChainId } from '@/types'
8
8
  import { ethers } from 'ethers';
9
+ import { encodeMulti, MetaTransaction, OperationType } from 'ethers-multisend';
10
+ import { Transaction } from '@/db';
11
+ import config from '@/config';
12
+ import abi from '@/abi';
13
+ import { InteropBridgeToken } from '@/typechain';
9
14
 
10
15
  export const http = axios.create();
11
16
 
@@ -119,11 +124,62 @@ export const asyncCallWithTimeout = async <T>(asyncPromise: Promise<T>, timeout:
119
124
  }
120
125
 
121
126
 
122
- export const generateInteropTransactionHash = (data: { type: string, sourceTransactionHash: string, sourceChainId: string | number, targetChainId: string | number }) => {
127
+ export const generateInteropTransactionHash = (data: { action: string, submitTransactionHash: string, sourceChainId: string | number, targetChainId: string | number }) => {
123
128
  return ethers.utils.solidityKeccak256(['string', 'string', 'string', 'string'], [
124
- String(data.type),
125
- String(data.sourceTransactionHash),
129
+ String(data.action),
130
+ String(data.submitTransactionHash),
126
131
  String(data.sourceChainId),
127
132
  String(data.targetChainId),
128
133
  ]);
129
- }
134
+ }
135
+
136
+ export const buildDataForTransaction = async (transaction: Transaction, type?: 'source' | 'target') => {
137
+ type = type || transaction.sourceStatus === 'pending' ? 'source' : 'target';
138
+
139
+ const transactions: MetaTransaction[] = [];
140
+
141
+ if(transaction.action != 'deposit') {
142
+ throw new Error('Invalid action');
143
+ }
144
+
145
+ if (transaction.action === 'deposit' && transaction.sourceStatus === 'pending') {
146
+ throw Error('Cannot build data for pending deposit transaction');
147
+ }
148
+
149
+ if (!transaction.submitEvent) {
150
+ throw Error('Cannot build data for transaction without submitEvent');
151
+ }
152
+
153
+
154
+ const token = tokens[transaction.sourceChainId].find(token => token.address.toLowerCase() === transaction.submitEvent.token.toLowerCase());
155
+
156
+ if (!token) {
157
+ throw Error('Cannot build data for transaction without token');
158
+ }
159
+
160
+ const itoken = itokens[transaction.targetChainId].find(itoken => itoken.symbol.toLowerCase() === token.symbol.toLowerCase());
161
+
162
+ if (!itoken) {
163
+ throw Error('Cannot build data for transaction without itoken');
164
+ }
165
+
166
+ const targetChainProvider = new ethers.providers.JsonRpcProvider(getRpcProviderUrl(transaction.targetChainId as ChainId));
167
+ const targetWallet = new ethers.Wallet(config.privateKey, targetChainProvider);
168
+ const interopBridgeContract = new ethers.Contract(itoken.address, abi.interopBridgeToken, targetWallet) as InteropBridgeToken;
169
+
170
+ const { data } = await interopBridgeContract.populateTransaction.mint(
171
+ transaction.submitEvent.to,
172
+ transaction.submitEvent.amount,
173
+ transaction.sourceChainId,
174
+ transaction.sourceTransactionHash,
175
+ );
176
+
177
+ transactions.push({
178
+ to: itoken.address,
179
+ data: data!,
180
+ value: '0',
181
+ operation: OperationType.Call,
182
+ });
183
+
184
+ return encodeMulti(transactions).data
185
+ }