feat: add onSignature pub sub api
This commit is contained in:
committed by
Michael Vines
parent
7ebf82b19d
commit
740b7a3b23
11
web3.js/module.d.ts
vendored
11
web3.js/module.d.ts
vendored
@ -114,6 +114,9 @@ declare module '@solana/web3.js' {
|
|||||||
keyedAccountInfo: KeyedAccountInfo,
|
keyedAccountInfo: KeyedAccountInfo,
|
||||||
) => void;
|
) => void;
|
||||||
export type SlotChangeCallback = (slotInfo: SlotInfo) => void;
|
export type SlotChangeCallback = (slotInfo: SlotInfo) => void;
|
||||||
|
export type SignatureResultCallback = (
|
||||||
|
signatureResult: SignatureStatusResult,
|
||||||
|
) => void;
|
||||||
|
|
||||||
export type SignatureSuccess = {
|
export type SignatureSuccess = {
|
||||||
Ok: null;
|
Ok: null;
|
||||||
@ -213,8 +216,14 @@ declare module '@solana/web3.js' {
|
|||||||
programId: PublicKey,
|
programId: PublicKey,
|
||||||
callback: ProgramAccountChangeCallback,
|
callback: ProgramAccountChangeCallback,
|
||||||
): number;
|
): number;
|
||||||
onSlotChange(callback: SlotChangeCallback): number;
|
|
||||||
removeProgramAccountChangeListener(id: number): Promise<void>;
|
removeProgramAccountChangeListener(id: number): Promise<void>;
|
||||||
|
onSlotChange(callback: SlotChangeCallback): number;
|
||||||
|
removeSlotChangeListener(id: number): Promise<void>;
|
||||||
|
onSignature(
|
||||||
|
signature: TransactionSignature,
|
||||||
|
callback: SignatureResultCallback,
|
||||||
|
): number;
|
||||||
|
removeSignatureListener(id: number): Promise<void>;
|
||||||
validatorExit(): Promise<boolean>;
|
validatorExit(): Promise<boolean>;
|
||||||
getMinimumBalanceForRentExemption(
|
getMinimumBalanceForRentExemption(
|
||||||
dataLength: number,
|
dataLength: number,
|
||||||
|
@ -58,8 +58,7 @@ declare module '@solana/web3.js' {
|
|||||||
|
|
||||||
declare export type SignatureStatusResult =
|
declare export type SignatureStatusResult =
|
||||||
| SignatureSuccess
|
| SignatureSuccess
|
||||||
| TransactionError
|
| TransactionError;
|
||||||
| null;
|
|
||||||
|
|
||||||
declare export type BlockhashAndFeeCalculator = {
|
declare export type BlockhashAndFeeCalculator = {
|
||||||
blockhash: Blockhash,
|
blockhash: Blockhash,
|
||||||
@ -96,7 +95,7 @@ declare module '@solana/web3.js' {
|
|||||||
fee: number,
|
fee: number,
|
||||||
preBalances: Array<number>,
|
preBalances: Array<number>,
|
||||||
postBalances: Array<number>,
|
postBalances: Array<number>,
|
||||||
status: SignatureStatusResult,
|
status: ?SignatureStatusResult,
|
||||||
},
|
},
|
||||||
}>,
|
}>,
|
||||||
};
|
};
|
||||||
@ -128,6 +127,9 @@ declare module '@solana/web3.js' {
|
|||||||
keyedAccountInfo: KeyedAccountInfo,
|
keyedAccountInfo: KeyedAccountInfo,
|
||||||
) => void;
|
) => void;
|
||||||
declare type SlotChangeCallback = (slotInfo: SlotInfo) => void;
|
declare type SlotChangeCallback = (slotInfo: SlotInfo) => void;
|
||||||
|
declare type SignatureResultCallback = (
|
||||||
|
signatureResult: SignatureStatusResult,
|
||||||
|
) => void;
|
||||||
|
|
||||||
declare export type SignatureSuccess = {|
|
declare export type SignatureSuccess = {|
|
||||||
Ok: null,
|
Ok: null,
|
||||||
@ -227,8 +229,14 @@ declare module '@solana/web3.js' {
|
|||||||
programId: PublicKey,
|
programId: PublicKey,
|
||||||
callback: ProgramAccountChangeCallback,
|
callback: ProgramAccountChangeCallback,
|
||||||
): number;
|
): number;
|
||||||
onSlotChange(callback: SlotChangeCallback): number;
|
|
||||||
removeProgramAccountChangeListener(id: number): Promise<void>;
|
removeProgramAccountChangeListener(id: number): Promise<void>;
|
||||||
|
onSlotChange(callback: SlotChangeCallback): number;
|
||||||
|
removeSlotChangeListener(id: number): Promise<void>;
|
||||||
|
onSignature(
|
||||||
|
signature: TransactionSignature,
|
||||||
|
callback: SignatureResultCallback,
|
||||||
|
): number;
|
||||||
|
removeSignatureListener(id: number): Promise<void>;
|
||||||
validatorExit(): Promise<boolean>;
|
validatorExit(): Promise<boolean>;
|
||||||
getMinimumBalanceForRentExemption(
|
getMinimumBalanceForRentExemption(
|
||||||
dataLength: number,
|
dataLength: number,
|
||||||
|
@ -175,6 +175,14 @@ const GetEpochScheduleResult = struct({
|
|||||||
firstNormalSlot: 'number',
|
firstNormalSlot: 'number',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signature status for a transaction
|
||||||
|
*/
|
||||||
|
const SignatureStatusResult = struct.union([
|
||||||
|
struct({Ok: 'null'}),
|
||||||
|
struct({Err: 'object'}),
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Version info for a node
|
* Version info for a node
|
||||||
*
|
*
|
||||||
@ -204,7 +212,7 @@ type ConfirmedBlock = {
|
|||||||
fee: number,
|
fee: number,
|
||||||
preBalances: Array<number>,
|
preBalances: Array<number>,
|
||||||
postBalances: Array<number>,
|
postBalances: Array<number>,
|
||||||
status: SignatureStatusResult,
|
status?: SignatureStatusResult,
|
||||||
},
|
},
|
||||||
}>,
|
}>,
|
||||||
};
|
};
|
||||||
@ -337,7 +345,7 @@ const SlotInfo = struct({
|
|||||||
root: 'number',
|
root: 'number',
|
||||||
});
|
});
|
||||||
|
|
||||||
/***
|
/**
|
||||||
* Expected JSON RPC response for the "slotNotification" message
|
* Expected JSON RPC response for the "slotNotification" message
|
||||||
*/
|
*/
|
||||||
const SlotNotificationResult = struct({
|
const SlotNotificationResult = struct({
|
||||||
@ -345,6 +353,14 @@ const SlotNotificationResult = struct({
|
|||||||
result: SlotInfo,
|
result: SlotInfo,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expected JSON RPC response for the "signatureNotification" message
|
||||||
|
*/
|
||||||
|
const SignatureNotificationResult = struct({
|
||||||
|
subscription: 'number',
|
||||||
|
result: SignatureStatusResult,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expected JSON RPC response for the "getProgramAccounts" message
|
* Expected JSON RPC response for the "getProgramAccounts" message
|
||||||
*/
|
*/
|
||||||
@ -419,15 +435,12 @@ const GetVoteAccounts = jsonRpcResult(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const SignatureStatusResult = struct.union([
|
|
||||||
'null',
|
|
||||||
struct.union([struct({Ok: 'null'}), struct({Err: 'object'})]),
|
|
||||||
]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expected JSON RPC response for the "getSignatureStatus" message
|
* Expected JSON RPC response for the "getSignatureStatus" message
|
||||||
*/
|
*/
|
||||||
const GetSignatureStatusRpcResult = jsonRpcResult(SignatureStatusResult);
|
const GetSignatureStatusRpcResult = jsonRpcResult(
|
||||||
|
struct.union(['null', SignatureStatusResult]),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expected JSON RPC response for the "getTransactionCount" message
|
* Expected JSON RPC response for the "getTransactionCount" message
|
||||||
@ -481,7 +494,7 @@ export const GetConfirmedBlockRpcResult = jsonRpcResult(
|
|||||||
meta: struct.union([
|
meta: struct.union([
|
||||||
'null',
|
'null',
|
||||||
struct({
|
struct({
|
||||||
status: SignatureStatusResult,
|
status: struct.union(['null', SignatureStatusResult]),
|
||||||
fee: 'number',
|
fee: 'number',
|
||||||
preBalances: struct.array(['number']),
|
preBalances: struct.array(['number']),
|
||||||
postBalances: struct.array(['number']),
|
postBalances: struct.array(['number']),
|
||||||
@ -578,6 +591,11 @@ type ProgramAccountSubscriptionInfo = {
|
|||||||
subscriptionId: null | number, // null when there's no current server subscription id
|
subscriptionId: null | number, // null when there's no current server subscription id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback function for slot change notifications
|
||||||
|
*/
|
||||||
|
export type SlotChangeCallback = (slotInfo: SlotInfo) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@ -587,9 +605,20 @@ type SlotSubscriptionInfo = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback function for slot change notifications
|
* Callback function for signature notifications
|
||||||
*/
|
*/
|
||||||
export type SlotChangeCallback = (slotInfo: SlotInfo) => void;
|
export type SignatureResultCallback = (
|
||||||
|
signatureResult: SignatureStatusResult,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
type SignatureSubscriptionInfo = {
|
||||||
|
signature: TransactionSignature, // TransactionSignature as a base 58 string
|
||||||
|
callback: SignatureResultCallback,
|
||||||
|
subscriptionId: null | number, // null when there's no current server subscription id
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signature status: Success
|
* Signature status: Success
|
||||||
@ -650,6 +679,10 @@ export class Connection {
|
|||||||
[number]: SlotSubscriptionInfo,
|
[number]: SlotSubscriptionInfo,
|
||||||
} = {};
|
} = {};
|
||||||
_slotSubscriptionCounter: number = 0;
|
_slotSubscriptionCounter: number = 0;
|
||||||
|
_signatureSubscriptions: {
|
||||||
|
[number]: SignatureSubscriptionInfo,
|
||||||
|
} = {};
|
||||||
|
_signatureSubscriptionCounter: number = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establish a JSON RPC connection
|
* Establish a JSON RPC connection
|
||||||
@ -693,6 +726,10 @@ export class Connection {
|
|||||||
'slotNotification',
|
'slotNotification',
|
||||||
this._wsOnSlotNotification.bind(this),
|
this._wsOnSlotNotification.bind(this),
|
||||||
);
|
);
|
||||||
|
this._rpcWebSocket.on(
|
||||||
|
'signatureNotification',
|
||||||
|
this._wsOnSignatureNotification.bind(this),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1289,10 +1326,12 @@ export class Connection {
|
|||||||
this._programAccountChangeSubscriptions,
|
this._programAccountChangeSubscriptions,
|
||||||
).map(Number);
|
).map(Number);
|
||||||
const slotKeys = Object.keys(this._slotSubscriptions).map(Number);
|
const slotKeys = Object.keys(this._slotSubscriptions).map(Number);
|
||||||
|
const signatureKeys = Object.keys(this._signatureSubscriptions).map(Number);
|
||||||
if (
|
if (
|
||||||
accountKeys.length === 0 &&
|
accountKeys.length === 0 &&
|
||||||
programKeys.length === 0 &&
|
programKeys.length === 0 &&
|
||||||
slotKeys.length === 0
|
slotKeys.length === 0 &&
|
||||||
|
signatureKeys.length === 0
|
||||||
) {
|
) {
|
||||||
this._rpcWebSocket.close();
|
this._rpcWebSocket.close();
|
||||||
return;
|
return;
|
||||||
@ -1308,6 +1347,9 @@ export class Connection {
|
|||||||
for (let id of slotKeys) {
|
for (let id of slotKeys) {
|
||||||
this._slotSubscriptions[id].subscriptionId = null;
|
this._slotSubscriptions[id].subscriptionId = null;
|
||||||
}
|
}
|
||||||
|
for (let id of signatureKeys) {
|
||||||
|
this._signatureSubscriptions[id].subscriptionId = null;
|
||||||
|
}
|
||||||
this._rpcWebSocket.connect();
|
this._rpcWebSocket.connect();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1359,6 +1401,21 @@ export class Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (let id of signatureKeys) {
|
||||||
|
const subscription = this._signatureSubscriptions[id];
|
||||||
|
if (subscription.subscriptionId === null) {
|
||||||
|
subscription.subscriptionId = -1; // indicates subscribing
|
||||||
|
try {
|
||||||
|
const id = await this._rpcWebSocket.call('signatureSubscribe', [
|
||||||
|
subscription.signature,
|
||||||
|
]);
|
||||||
|
subscription.subscriptionId = id;
|
||||||
|
} catch (err) {
|
||||||
|
subscription.subscriptionId = null;
|
||||||
|
console.log(`signatureSubscribe error: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1577,4 +1634,72 @@ export class Connection {
|
|||||||
}
|
}
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_wsOnSignatureNotification(notification: Object) {
|
||||||
|
const res = SignatureNotificationResult(notification);
|
||||||
|
if (res.error) {
|
||||||
|
throw new Error(res.error.message);
|
||||||
|
}
|
||||||
|
assert(typeof res.result !== 'undefined');
|
||||||
|
|
||||||
|
const keys = Object.keys(this._signatureSubscriptions).map(Number);
|
||||||
|
for (let id of keys) {
|
||||||
|
const sub = this._signatureSubscriptions[id];
|
||||||
|
if (sub.subscriptionId === res.subscription) {
|
||||||
|
// Signatures subscriptions are auto-removed by the RPC service so
|
||||||
|
// no need to explicitly send an unsubscribe message
|
||||||
|
delete this._signatureSubscriptions[id];
|
||||||
|
this._updateSubscriptions();
|
||||||
|
sub.callback(res.result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a callback to be invoked upon signature updates
|
||||||
|
*
|
||||||
|
* @param callback Function to invoke on signature notifications
|
||||||
|
* @return subscription id
|
||||||
|
*/
|
||||||
|
onSignature(
|
||||||
|
signature: TransactionSignature,
|
||||||
|
callback: SignatureResultCallback,
|
||||||
|
): number {
|
||||||
|
const id = ++this._signatureSubscriptionCounter;
|
||||||
|
this._signatureSubscriptions[id] = {
|
||||||
|
signature,
|
||||||
|
callback,
|
||||||
|
subscriptionId: null,
|
||||||
|
};
|
||||||
|
this._updateSubscriptions();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deregister a signature notification callback
|
||||||
|
*
|
||||||
|
* @param id subscription id to deregister
|
||||||
|
*/
|
||||||
|
async removeSignatureListener(id: number): Promise<void> {
|
||||||
|
if (this._signatureSubscriptions[id]) {
|
||||||
|
const {subscriptionId} = this._signatureSubscriptions[id];
|
||||||
|
delete this._signatureSubscriptions[id];
|
||||||
|
if (subscriptionId !== null) {
|
||||||
|
try {
|
||||||
|
await this._rpcWebSocket.call('signatureUnsubscribe', [
|
||||||
|
subscriptionId,
|
||||||
|
]);
|
||||||
|
} catch (err) {
|
||||||
|
console.log('signatureUnsubscribe error:', err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._updateSubscriptions();
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown signature change id: ${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user