mirror of
https://github.com/alexgo-io/stacks-blockchain-api.git
synced 2026-04-29 05:15:32 +08:00
feat: extract newly deployed contract name and Clarity source from core-node event stream
This commit is contained in:
@@ -3,6 +3,10 @@ import { SmartBuffer } from 'smart-buffer';
|
||||
import { isEnum } from './helpers';
|
||||
|
||||
export class BufferReader extends SmartBuffer {
|
||||
static fromBuffer(buffer: Buffer): BufferReader {
|
||||
return new BufferReader({ buff: buffer });
|
||||
}
|
||||
|
||||
readBigUIntLE(length: number): bigint {
|
||||
const buffer = Buffer.from(this.readBuffer(length)).reverse();
|
||||
const hex = buffer.toString('hex');
|
||||
|
||||
@@ -9,11 +9,7 @@ export enum CoreNodeEventType {
|
||||
FtMintEvent = 'ft_mint_event',
|
||||
}
|
||||
|
||||
export interface CoreNodeEventMessage {
|
||||
type: CoreNodeEventType;
|
||||
}
|
||||
|
||||
export interface SmartContractEvent extends CoreNodeEventMessage {
|
||||
export interface SmartContractEvent {
|
||||
type: CoreNodeEventType.ContractEvent;
|
||||
contract_event: {
|
||||
contract_identifier: string;
|
||||
@@ -22,46 +18,56 @@ export interface SmartContractEvent extends CoreNodeEventMessage {
|
||||
};
|
||||
}
|
||||
|
||||
export interface StxTransferEvent extends CoreNodeEventMessage {
|
||||
export interface StxTransferEvent {
|
||||
type: CoreNodeEventType.StxTransferEvent;
|
||||
stx_transfer_event: any;
|
||||
}
|
||||
|
||||
export interface StxMintEvent extends CoreNodeEventMessage {
|
||||
export interface StxMintEvent {
|
||||
type: CoreNodeEventType.StxMintEvent;
|
||||
stx_mint_event: any;
|
||||
}
|
||||
|
||||
export interface StxBurnEvent extends CoreNodeEventMessage {
|
||||
export interface StxBurnEvent {
|
||||
type: CoreNodeEventType.StxBurnEvent;
|
||||
stx_burn_event: any;
|
||||
}
|
||||
|
||||
export interface NftTransferEvent extends CoreNodeEventMessage {
|
||||
export interface NftTransferEvent {
|
||||
type: CoreNodeEventType.NftTransferEvent;
|
||||
nft_transfer_event: any;
|
||||
}
|
||||
|
||||
export interface NftMintEvent extends CoreNodeEventMessage {
|
||||
export interface NftMintEvent {
|
||||
type: CoreNodeEventType.NftMintEvent;
|
||||
nft_mint_event: any;
|
||||
}
|
||||
|
||||
export interface FtTransferEvent extends CoreNodeEventMessage {
|
||||
export interface FtTransferEvent {
|
||||
type: CoreNodeEventType.FtTransferEvent;
|
||||
ft_transfer_event: any;
|
||||
}
|
||||
|
||||
export interface FtMintEvent extends CoreNodeEventMessage {
|
||||
export interface FtMintEvent {
|
||||
type: CoreNodeEventType.FtMintEvent;
|
||||
ft_mint_event: any;
|
||||
}
|
||||
|
||||
export type CoreNodeEvent =
|
||||
| SmartContractEvent
|
||||
| StxTransferEvent
|
||||
| StxMintEvent
|
||||
| StxBurnEvent
|
||||
| FtTransferEvent
|
||||
| FtMintEvent
|
||||
| NftTransferEvent
|
||||
| NftMintEvent;
|
||||
|
||||
export interface CoreNodeMessage {
|
||||
block_hash: string;
|
||||
index_block_hash: string;
|
||||
parent_block_hash: string;
|
||||
parent_microblock: string;
|
||||
events: any[];
|
||||
events: CoreNodeEvent[];
|
||||
transactions: string[];
|
||||
}
|
||||
|
||||
50
src/index.ts
50
src/index.ts
@@ -1,13 +1,55 @@
|
||||
import * as net from 'net';
|
||||
import { readMessageFromSocket } from './event-stream/reader';
|
||||
import { CoreNodeMessage } from './event-stream/core-node-message';
|
||||
import { Transaction, readTransaction, TransactionPayloadTypeID } from './p2p/tx';
|
||||
import { BufferReader } from './binary-reader';
|
||||
import { NotImplementedError } from './errors';
|
||||
import { getEnumDescription } from './helpers';
|
||||
|
||||
const server = net.createServer(clientSocket => {
|
||||
console.log('client connected');
|
||||
readMessageFromSocket(clientSocket).catch(error => {
|
||||
async function handleClientMessage(clientSocket: net.Socket): Promise<void> {
|
||||
let msg: CoreNodeMessage;
|
||||
const txs: Transaction[] = [];
|
||||
try {
|
||||
msg = await readMessageFromSocket(clientSocket);
|
||||
} catch (error) {
|
||||
console.error(`error reading messages from socket: ${error}`);
|
||||
console.error(error);
|
||||
clientSocket.destroy();
|
||||
server.close();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
for (const tx of msg.transactions) {
|
||||
const txBuffer = Buffer.from(tx.substring(2), 'hex');
|
||||
const bufferReader = BufferReader.fromBuffer(txBuffer);
|
||||
const parsedTx = readTransaction(bufferReader);
|
||||
txs.push(parsedTx);
|
||||
console.log(parsedTx);
|
||||
switch (parsedTx.payload.typeId) {
|
||||
case TransactionPayloadTypeID.Coinbase: {
|
||||
break;
|
||||
}
|
||||
case TransactionPayloadTypeID.SmartContract: {
|
||||
console.log(`Smart contract deployed: ${parsedTx.payload.name}: ${parsedTx.payload.codeBody}`);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new NotImplementedError(
|
||||
`extracting data for tx type: ${getEnumDescription(TransactionPayloadTypeID, parsedTx.payload.typeId)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`error parsing message transactions: ${error}`);
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
const server = net.createServer(clientSocket => {
|
||||
console.log('client connected');
|
||||
handleClientMessage(clientSocket).catch(error => {
|
||||
console.error(`error processing socket connection: ${error}`);
|
||||
console.error(error);
|
||||
});
|
||||
clientSocket.on('end', () => {
|
||||
console.log('client disconnected');
|
||||
|
||||
@@ -72,6 +72,15 @@ interface TransactionAuthSponsored {
|
||||
sponsorCondition: TransactionSpendingCondition;
|
||||
}
|
||||
|
||||
enum TransactionAnchorMode {
|
||||
/** must be included in a StacksBlock */
|
||||
OnChainOnly = 1,
|
||||
/** must be included in a StacksMicroBlock */
|
||||
OffChainOnly = 2,
|
||||
/** either */
|
||||
Any = 3,
|
||||
}
|
||||
|
||||
enum TransactionPostConditionMode {
|
||||
Allow = 0x01,
|
||||
Deny = 0x02,
|
||||
@@ -171,7 +180,7 @@ type TransactionPostCondition =
|
||||
| TransactionPostConditionFungible
|
||||
| TransactionPostConditionNonfungible;
|
||||
|
||||
enum TransactionPayloadTypeID {
|
||||
export enum TransactionPayloadTypeID {
|
||||
TokenTransfer = 0,
|
||||
SmartContract = 1,
|
||||
ContractCall = 2,
|
||||
@@ -196,9 +205,10 @@ interface TransactionPayloadContractCall {
|
||||
typeId: TransactionPayloadTypeID.ContractCall;
|
||||
}
|
||||
|
||||
// TODO: incomplete
|
||||
interface TransactionPayloadSmartContract {
|
||||
typeId: TransactionPayloadTypeID.SmartContract;
|
||||
name: string;
|
||||
codeBody: string;
|
||||
}
|
||||
|
||||
// TODO: incomplete
|
||||
@@ -217,7 +227,7 @@ export interface Transaction {
|
||||
version: TransactionVersion; // u8
|
||||
chainId: number; // u32
|
||||
auth: TransactionAuthStandard | TransactionAuthSponsored;
|
||||
anchorMode: TransactionPostConditionMode; // u8
|
||||
anchorMode: TransactionAnchorMode; // u8
|
||||
postConditionMode: TransactionPostConditionMode; // u8
|
||||
postConditions: TransactionPostCondition[];
|
||||
payload: TransactionPayload;
|
||||
@@ -253,7 +263,7 @@ export function readTransaction(reader: BufferReader): Transaction {
|
||||
throw new NotImplementedError(`tx auth type: ${getEnumDescription(TransactionAuthTypeID, authType)}`);
|
||||
}
|
||||
|
||||
const anchorMode = reader.readUInt8Enum(TransactionPostConditionMode, n => {
|
||||
const anchorMode = reader.readUInt8Enum(TransactionAnchorMode, n => {
|
||||
throw new StacksMessageParsingError(`unexpected tx post condition anchor mode: ${n}`);
|
||||
});
|
||||
|
||||
@@ -300,19 +310,43 @@ function readTransactionPayload(reader: BufferReader): TransactionPayload {
|
||||
} else if (txPayloadType === TransactionPayloadTypeID.TokenTransfer) {
|
||||
const payload: TransactionPayloadTokenTransfer = {
|
||||
typeId: txPayloadType,
|
||||
address: {
|
||||
version: reader.readUInt8(),
|
||||
bytes: reader.readBuffer(20),
|
||||
},
|
||||
address: readStacksAddress(reader),
|
||||
amount: reader.readBigInt64BE(),
|
||||
memo: reader.readBuffer(34),
|
||||
};
|
||||
return payload;
|
||||
} else if (txPayloadType === TransactionPayloadTypeID.SmartContract) {
|
||||
const payload: TransactionPayloadSmartContract = {
|
||||
typeId: txPayloadType,
|
||||
name: readContractName(reader),
|
||||
codeBody: readString(reader),
|
||||
};
|
||||
return payload;
|
||||
} else {
|
||||
throw new NotImplementedError(`tx payload type: ${getEnumDescription(TransactionPayloadTypeID, txPayloadType)}`);
|
||||
}
|
||||
}
|
||||
|
||||
function readString(reader: BufferReader): string {
|
||||
const length = reader.readUInt32BE();
|
||||
const str = reader.readString(length, 'ascii');
|
||||
return str;
|
||||
}
|
||||
|
||||
function readContractName(reader: BufferReader): string {
|
||||
const length = reader.readUInt8();
|
||||
const name = reader.readString(length, 'ascii');
|
||||
return name;
|
||||
}
|
||||
|
||||
function readStacksAddress(reader: BufferReader): StacksAddress {
|
||||
const address: StacksAddress = {
|
||||
version: reader.readUInt8(),
|
||||
bytes: reader.readBuffer(20),
|
||||
};
|
||||
return address;
|
||||
}
|
||||
|
||||
function readTransactionPostConditions(reader: BufferReader): TransactionPostCondition[] {
|
||||
const conditionCount = reader.readUInt32BE();
|
||||
const conditions = new Array<TransactionPostCondition>(conditionCount);
|
||||
@@ -349,23 +383,16 @@ function readTransactionPostConditionPrincipal(reader: BufferReader): PostCondit
|
||||
} else if (typeId === PostConditionPrincipalTypeID.Standard) {
|
||||
const principal: PostConditionPrincipalStandard = {
|
||||
typeId: typeId,
|
||||
address: {
|
||||
version: reader.readUInt8(),
|
||||
bytes: reader.readBuffer(20),
|
||||
},
|
||||
address: readStacksAddress(reader),
|
||||
};
|
||||
return principal;
|
||||
} else if (typeId === PostConditionPrincipalTypeID.Contract) {
|
||||
const address: StacksAddress = {
|
||||
version: reader.readUInt8(),
|
||||
bytes: reader.readBuffer(20),
|
||||
};
|
||||
const contractNameLen = reader.readUInt8();
|
||||
const contractNameBuff = reader.readBuffer(contractNameLen);
|
||||
const address = readStacksAddress(reader);
|
||||
const contractName = readContractName(reader);
|
||||
const principal: PostConditionPrincipalContract = {
|
||||
typeId: typeId,
|
||||
address: address,
|
||||
contractName: contractNameBuff.toString('ascii'),
|
||||
contractName: contractName,
|
||||
};
|
||||
return principal;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user