@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.
- package/dist/package.json +69 -0
- package/dist/{abi → src/abi}/erc20.json +0 -0
- package/dist/{abi → src/abi}/gnosisSafe.json +0 -0
- package/dist/{abi → src/abi}/index.js +0 -0
- package/dist/{abi → src/abi}/interopBridgeToken.json +0 -0
- package/dist/{abi → src/abi}/interopXGateway.json +0 -0
- package/dist/src/api/index.js +33 -0
- package/dist/{config → src/config}/index.js +0 -0
- package/dist/{constants → src/constants}/addresses.js +0 -8
- package/dist/{constants → src/constants}/index.js +1 -0
- package/dist/src/constants/itokens.js +13 -0
- package/dist/{constants → src/constants}/tokens.js +0 -0
- package/dist/{db → src/db}/index.js +0 -0
- package/dist/{db → src/db}/models/index.js +0 -0
- package/dist/{db → src/db}/models/transaction.js +3 -1
- package/dist/{db → src/db}/sequelize.js +0 -0
- package/dist/{index.js → src/index.js} +5 -0
- package/dist/{logger → src/logger}/index.js +0 -0
- package/dist/{net → src/net}/index.js +0 -0
- package/dist/{net → src/net}/peer/index.js +0 -0
- package/dist/{net → src/net}/pool/index.js +0 -0
- package/dist/{net → src/net}/protocol/dial/BaseDialProtocol.js +0 -0
- package/dist/{net → src/net}/protocol/dial/SignatureDialProtocol.js +15 -12
- package/dist/{net → src/net}/protocol/index.js +0 -0
- package/dist/{tasks → src/tasks}/BaseTask.js +0 -0
- package/dist/src/tasks/InteropXGateway/ProcessDepositEvents.js +140 -0
- package/dist/{tasks → src/tasks}/InteropXGateway/SyncDepositEvents.js +13 -17
- package/dist/{tasks → src/tasks}/index.js +0 -0
- package/dist/{typechain → src/typechain}/Erc20.js +0 -0
- package/dist/{typechain → src/typechain}/GnosisSafe.js +0 -0
- package/dist/{typechain → src/typechain}/InteropBridgeToken.js +0 -0
- package/dist/{typechain → src/typechain}/InteropXGateway.js +0 -0
- package/dist/{typechain → src/typechain}/common.js +0 -0
- package/dist/{typechain → src/typechain}/factories/Erc20__factory.js +0 -0
- package/dist/{typechain → src/typechain}/factories/GnosisSafe__factory.js +0 -0
- package/dist/{typechain → src/typechain}/factories/InteropBridgeToken__factory.js +0 -0
- package/dist/{typechain → src/typechain}/factories/InteropXGateway__factory.js +0 -0
- package/dist/{typechain → src/typechain}/factories/index.js +0 -0
- package/dist/{typechain → src/typechain}/index.js +0 -0
- package/dist/{types.js → src/types.js} +0 -0
- package/dist/{utils → src/utils}/index.js +39 -3
- package/package.json +5 -2
- package/src/api/index.ts +33 -0
- package/src/constants/addresses.ts +0 -8
- package/src/constants/index.ts +1 -0
- package/src/constants/itokens.ts +10 -0
- package/src/db/models/transaction.ts +8 -4
- package/src/index.ts +7 -0
- package/src/net/protocol/dial/SignatureDialProtocol.ts +17 -13
- package/src/tasks/InteropXGateway/ProcessDepositEvents.ts +232 -0
- package/src/tasks/InteropXGateway/SyncDepositEvents.ts +18 -6
- 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
|
-
|
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
|
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
|
20
|
+
let transaction;
|
19
21
|
let maxTimeout = 20000;
|
20
22
|
do {
|
21
|
-
|
22
|
-
if (!
|
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 (!
|
27
|
-
if (!
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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:
|
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
|
-
|
33
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
File without changes
|
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.
|
96
|
-
String(data.
|
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.
|
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",
|
package/src/api/index.ts
ADDED
@@ -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
|
}
|
package/src/constants/index.ts
CHANGED
@@ -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
|
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
|
-
|
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:
|
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
|
30
|
+
let transaction: Transaction | null;
|
28
31
|
let maxTimeout = 20000;
|
29
32
|
|
30
33
|
do {
|
31
|
-
|
34
|
+
transaction = await Transaction.findOne({ where: { transactionHash: data.transactionHash } })
|
32
35
|
|
33
|
-
if (!
|
36
|
+
if (!transaction) {
|
34
37
|
await wait(1000);
|
35
38
|
maxTimeout -= 1000;
|
36
39
|
}
|
37
|
-
} while (!
|
40
|
+
} while (!transaction && maxTimeout > 0)
|
38
41
|
|
39
|
-
if (!
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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:
|
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
|
-
|
47
|
-
|
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
|
-
|
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: "
|
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
|
|
package/src/utils/index.ts
CHANGED
@@ -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: {
|
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.
|
125
|
-
String(data.
|
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
|
+
}
|