Skip to content

Commit

Permalink
feat: add chain event processors fro GMP API
Browse files Browse the repository at this point in the history
  • Loading branch information
sdaveas committed Sep 13, 2024
1 parent 37a834f commit 89d417d
Show file tree
Hide file tree
Showing 6 changed files with 380 additions and 3 deletions.
40 changes: 40 additions & 0 deletions examples/amplifier/amplifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ const { savePayload } = require('./endpoints/save-payload.js');
const { subscribe_to_approvals } = require('./endpoints/subscribe-to-approvals.js');
const { subscribe_to_wasm_events } = require('./endpoints/subscribe-to-wasm-events.js');
const { verify } = require('./endpoints/verify.js');
const { processContractCallEvent } = require('./gmp-api/contract-call-event.js');
const { processMessageApprovedEvent } = require('./gmp-api/approve-event.js');
const { processMessageExecutedEvent } = require('./gmp-api/execute-event.js');

const program = new commander.Command();

Expand Down Expand Up @@ -65,4 +68,41 @@ program
verify(options.id, options.sourceChain, options.sourceAddress, options.destinationChain, options.destinationAddress, options.payload);
});

program
.command('process-contract-call-event')
.requiredOption("--source-chain <source chain>", "The source chain")
.requiredOption("--tx-hash <transaction hash>", "The transaction hash")
.option("--dry-run", "Dry run the process")
.action((options) => {
processContractCallEvent(options.sourceChain, options.txHash, options.dryRun)
.then(() => console.log('Process completed successfully'))
.catch(error => console.error('Process failed:', error));
});

program
.command('process-approve-event')
.requiredOption("--destination-chain <destination chain>", "The destination chain")
.requiredOption("--tx-hash <transaction hash>", "The transaction hash")
.option("--amount <amount>", "Remaining gas amount")
.option("--dry-run", "Dry run the process")
.action((options) => {
processMessageApprovedEvent(options.destinationChain, options.txHash, options.amount, options.dryRun)
.then(() => console.log('Process completed successfully'))
.catch(error => console.error('Process failed:', error));
});

program
.command('process-execute-event')
.requiredOption("--destination-chain <destination chain>", "The destination chain")
.requiredOption("--tx-hash <transaction hash>", "The transaction hash")
.requiredOption("--source-chain <source chain>", "The source chain")
.requiredOption("--message-id <message id>", "The message id")
.option("--amount <amount>", "Remaining gas amount")
.option("--dry-run", "Dry run the process")
.action((options) => {
processMessageExecutedEvent(options.destinationChain, options.txHash, options.sourceChain, options.messageId, options.amount, options.dryRun)
.then(() => console.log('Process completed successfully'))
.catch(error => console.error('Process failed:', error));
});

program.parse();
10 changes: 10 additions & 0 deletions examples/amplifier/chains.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"name": "test-ethereum",
"rpc": "https://ethereum-sepolia.rpc.subquery.network/public"
},
{
"name": "test-avalanche",
"rpc": "https://ava-testnet.public.blastapi.io/ext/bc/C/rpc"
}
]
32 changes: 29 additions & 3 deletions examples/amplifier/config.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,48 @@
const fs = require('fs');

const dotenv = require('dotenv');

// Load environment variables from .env file
dotenv.config();

// Default configuration values
const defaults = {
// gRPC
HOST: "localhost",
PORT: "50051"
PORT: "50051",

// GMP API
GMP_API_URL: "http://localhost:8080",
};

function getConfig() {
const serverHOST = process.env.HOST || defaults.HOST;
const serverPort = process.env.PORT || defaults.PORT;

const gmpAPIURL = process.env.GMP_API_URL || defaults.GMP_API_URL;

return {
serverHOST,
serverPort
serverPort,
gmpAPIURL,
};
}

module.exports = getConfig;
const chainsConfigFile = './chains.json';

function getChainConfig(chainName) {
const chainsConfig = JSON.parse(fs.readFileSync(chainsConfigFile, 'utf8'));

const chainConfig = chainsConfig.find(c => c.name === chainName);

if (!chainConfig) {
throw new Error(`RPC URL not found for chain: ${chainName}`);
}

return chainConfig;
}

module.exports = {
getConfig,
getChainConfig,
};
104 changes: 104 additions & 0 deletions examples/amplifier/gmp-api/approve-event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
const axios = require('axios');
const { ethers } = require('ethers');
const { getConfig, getChainConfig } = require('../config.js');

const { GMP_API_URL } = getConfig();

// ABI for the ContractCall and MessageApproved events
const eventABI = [
"event MessageApproved(bytes32 indexed commandId, string sourceChain, string messageId, string sourceAddress, address indexed contractAddress, bytes32 indexed payloadHash)",
];

const iface = new ethers.utils.Interface(eventABI);

async function processMessageApprovedEvent(sourceChain, txHash, costAmount = "0", dryRun = false) {
apiEvent = await constructAPIEvent(sourceChain, txHash, costAmount);
console.log(apiEvent);

if (dryRun === true) {
return;
}

response = submitApproveEvent(apiEvent);
console.log(response);
}

async function constructAPIEvent(destinationChain, txHash, costAmount) {
try {
const destinationChainConfig = getChainConfig(destinationChain);

const provider = new ethers.providers.JsonRpcProvider(destinationChainConfig.rpc);

// Fetch transaction receipt
const receipt = await provider.getTransactionReceipt(txHash);

if (!receipt) {
throw new Error('Transaction receipt not found');
}

// Find the relevant log
const TOPIC = iface.getEventTopic('MessageApproved');
const relevantLog = receipt.logs.find(log => log.topics[0] === TOPIC);

if (!relevantLog) {
throw new Error('Relevant log not found');
}

// Decode the event data
const decodedLog = iface.parseLog(relevantLog);

// Extract data from the decoded log
const eventIndex = receipt.logs.indexOf(relevantLog);
const eventID = `${txHash}-${eventIndex}`;
const commandId = decodedLog.args.commandId;
const messageId = decodedLog.args.messageId;
const sourceChain = decodedLog.args.sourceChain;
const sourceAddress = decodedLog.args.sourceAddress;
const contractAddress = decodedLog.args.contractAddress;
const payloadHash = ethers.utils.base64.encode(decodedLog.args.payloadHash);

// Fetch block data for timestamp
const block = await provider.getBlock(receipt.blockNumber);
const timestamp = new Date(block.timestamp * 1000).toISOString();

// Construct the event object
return {
cost: {
amount: costAmount,
},
eventID,
message: {
destinationAddress: contractAddress,
messageID: messageId,
payloadHash,
sourceAddress,
sourceChain,
},
meta: {
commandID: commandId,
finalized: true,
fromAddress: receipt.from,
timestamp,
txID: txHash,
},
type: 'MESSAGE_APPROVED',
};
} catch (error) {
console.error('Error:', error.message);
throw error;
}
}

async function submitApproveEvent(apiEvent) {
destinationChain = apiEvent.destinationChain;
const response = await axios.post(`${GMP_API_URL}/chains/${destinationChain}/events`, {
events: [apiEvent],
});

console.log('API Response:', response.data);
return response.data;
}

module.exports = {
processMessageApprovedEvent,
};
101 changes: 101 additions & 0 deletions examples/amplifier/gmp-api/contract-call-event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
const axios = require('axios');
const { ethers } = require('ethers');
const { getConfig, getChainConfig } = require('../config.js');

const { GMP_API_URL } = getConfig();

// ABI for the ContractCall event
const eventABI = [
"event ContractCall(address indexed sender, string destinationChain, string destinationContractAddress, bytes32 indexed payloadHash, bytes payload)",
];

const iface = new ethers.utils.Interface(eventABI);

async function processContractCallEvent(sourceChain, txHash, dryRun = false) {
apiEvent = await constructAPIEvent(sourceChain, txHash);
console.log(apiEvent);

if (dryRun === true) {
return;
}

response = submitContractCallEvent(apiEvent);
console.log(response);
}

async function constructAPIEvent(sourceChain, txHash) {
try {
const sourceChainConfig = getChainConfig(sourceChain);

const provider = new ethers.providers.JsonRpcProvider(sourceChainConfig.rpc);

// Fetch transaction receipt
const receipt = await provider.getTransactionReceipt(txHash);

if (!receipt) {
throw new Error('Transaction receipt not found');
}

// Find the relevant log
const TOPIC = iface.getEventTopic('ContractCall');
const relevantLog = receipt.logs.find(log => log.topics[0] === TOPIC);

if (!relevantLog) {
throw new Error('Relevant log not found');
}

// Decode the event data
const decodedLog = iface.parseLog(relevantLog);

// Extract data from the decoded log
const eventIndex = receipt.logs.indexOf(relevantLog);
const eventID = `${txHash}-${eventIndex}`;
const sourceAddress = decodedLog.args.sender;
const destinationChain = decodedLog.args.destinationChain;
const destinationAddress = decodedLog.args.destinationContractAddress;
const payloadHash = ethers.utils.base64.encode(decodedLog.args.payloadHash);
const payload = ethers.utils.base64.encode(decodedLog.args.payload);

// Fetch block data for timestamp
const block = await provider.getBlock(receipt.blockNumber);
const timestamp = new Date(block.timestamp * 1000).toISOString();

// Construct the event object
return {
destinationChain,
eventID,
message: {
destinationAddress,
messageID: eventID,
payloadHash,
sourceAddress,
sourceChain,
},
meta: {
finalized: true, // Set to true if the event is finalized
fromAddress: receipt.from,
timestamp,
txID: txHash,
},
payload,
type: 'CALL',
};
} catch (error) {
console.error('Error:', error.message);
throw error;
}
}

async function submitContractCallEvent(apiEvent) {
sourceChain = apiEvent.message.sourceChain;
const response = await axios.post(`${GMP_API_URL}/chains/${sourceChain}/events`, {
events: [apiEvent],
});

console.log('API Response:', response.data);
return response.data;
}

module.exports = {
processContractCallEvent,
};
Loading

0 comments on commit 89d417d

Please sign in to comment.