-
Notifications
You must be signed in to change notification settings - Fork 219
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(pyth-lazer-sdk): add ed25519 ix builder function (#2203)
* feat(pyth-lazer-sdk): add ed25519 ix builder function * fix * fix
- Loading branch information
1 parent
f0659ce
commit 57670ca
Showing
5 changed files
with
243 additions
and
474 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import WebSocket from "isomorphic-ws"; | ||
|
||
import { | ||
BINARY_UPDATE_FORMAT_MAGIC, | ||
EVM_FORMAT_MAGIC, | ||
PARSED_FORMAT_MAGIC, | ||
type ParsedPayload, | ||
type Request, | ||
type Response, | ||
SOLANA_FORMAT_MAGIC_BE, | ||
} from "./protocol.js"; | ||
|
||
export type BinaryResponse = { | ||
subscriptionId: number; | ||
evm?: Buffer | undefined; | ||
solana?: Buffer | undefined; | ||
parsed?: ParsedPayload | undefined; | ||
}; | ||
export type JsonOrBinaryResponse = | ||
| { | ||
type: "json"; | ||
value: Response; | ||
} | ||
| { type: "binary"; value: BinaryResponse }; | ||
|
||
const UINT16_NUM_BYTES = 2; | ||
const UINT32_NUM_BYTES = 4; | ||
const UINT64_NUM_BYTES = 8; | ||
|
||
export class PythLazerClient { | ||
ws: WebSocket; | ||
|
||
constructor(url: string, token: string) { | ||
const finalUrl = new URL(url); | ||
finalUrl.searchParams.append("ACCESS_TOKEN", token); | ||
this.ws = new WebSocket(finalUrl); | ||
} | ||
|
||
addMessageListener(handler: (event: JsonOrBinaryResponse) => void) { | ||
this.ws.addEventListener("message", (event: WebSocket.MessageEvent) => { | ||
if (typeof event.data == "string") { | ||
handler({ | ||
type: "json", | ||
value: JSON.parse(event.data) as Response, | ||
}); | ||
} else if (Buffer.isBuffer(event.data)) { | ||
let pos = 0; | ||
const magic = event.data | ||
.subarray(pos, pos + UINT32_NUM_BYTES) | ||
.readUint32BE(); | ||
pos += UINT32_NUM_BYTES; | ||
if (magic != BINARY_UPDATE_FORMAT_MAGIC) { | ||
throw new Error("binary update format magic mismatch"); | ||
} | ||
// TODO: some uint64 values may not be representable as Number. | ||
const subscriptionId = Number( | ||
event.data.subarray(pos, pos + UINT64_NUM_BYTES).readBigInt64BE() | ||
); | ||
pos += UINT64_NUM_BYTES; | ||
|
||
const value: BinaryResponse = { subscriptionId }; | ||
while (pos < event.data.length) { | ||
const len = event.data | ||
.subarray(pos, pos + UINT16_NUM_BYTES) | ||
.readUint16BE(); | ||
pos += UINT16_NUM_BYTES; | ||
const magic = event.data | ||
.subarray(pos, pos + UINT32_NUM_BYTES) | ||
.readUint32BE(); | ||
if (magic == EVM_FORMAT_MAGIC) { | ||
value.evm = event.data.subarray(pos, pos + len); | ||
} else if (magic == SOLANA_FORMAT_MAGIC_BE) { | ||
value.solana = event.data.subarray(pos, pos + len); | ||
} else if (magic == PARSED_FORMAT_MAGIC) { | ||
value.parsed = JSON.parse( | ||
event.data.subarray(pos + UINT32_NUM_BYTES, pos + len).toString() | ||
) as ParsedPayload; | ||
} else { | ||
throw new Error("unknown magic: " + magic.toString()); | ||
} | ||
pos += len; | ||
} | ||
handler({ type: "binary", value }); | ||
} else { | ||
throw new TypeError("unexpected event data type"); | ||
} | ||
}); | ||
} | ||
|
||
send(request: Request) { | ||
this.ws.send(JSON.stringify(request)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import * as BufferLayout from "@solana/buffer-layout"; | ||
import { Ed25519Program, TransactionInstruction } from "@solana/web3.js"; | ||
|
||
const ED25519_INSTRUCTION_LEN = 16; | ||
const SIGNATURE_LEN = 64; | ||
const PUBKEY_LEN = 32; | ||
const MAGIC_LEN = 4; | ||
const MESSAGE_SIZE_LEN = 2; | ||
|
||
const ED25519_INSTRUCTION_LAYOUT = BufferLayout.struct< | ||
Readonly<{ | ||
messageDataOffset: number; | ||
messageDataSize: number; | ||
messageInstructionIndex: number; | ||
numSignatures: number; | ||
padding: number; | ||
publicKeyInstructionIndex: number; | ||
publicKeyOffset: number; | ||
signatureInstructionIndex: number; | ||
signatureOffset: number; | ||
}> | ||
>([ | ||
BufferLayout.u8("numSignatures"), | ||
BufferLayout.u8("padding"), | ||
BufferLayout.u16("signatureOffset"), | ||
BufferLayout.u16("signatureInstructionIndex"), | ||
BufferLayout.u16("publicKeyOffset"), | ||
BufferLayout.u16("publicKeyInstructionIndex"), | ||
BufferLayout.u16("messageDataOffset"), | ||
BufferLayout.u16("messageDataSize"), | ||
BufferLayout.u16("messageInstructionIndex"), | ||
]); | ||
|
||
export const createEd25519Instruction = ( | ||
message: Buffer, | ||
instructionIndex: number, | ||
startingOffset: number | ||
) => { | ||
const signatureOffset = startingOffset + MAGIC_LEN; | ||
const publicKeyOffset = signatureOffset + SIGNATURE_LEN; | ||
const messageDataSizeOffset = publicKeyOffset + PUBKEY_LEN; | ||
const messageDataOffset = messageDataSizeOffset + MESSAGE_SIZE_LEN; | ||
|
||
const messageDataSize = message.readUInt16LE( | ||
messageDataSizeOffset - startingOffset | ||
); | ||
|
||
const instructionData = Buffer.alloc(ED25519_INSTRUCTION_LEN); | ||
|
||
ED25519_INSTRUCTION_LAYOUT.encode( | ||
{ | ||
numSignatures: 1, | ||
padding: 0, | ||
signatureOffset, | ||
signatureInstructionIndex: instructionIndex, | ||
publicKeyOffset, | ||
publicKeyInstructionIndex: instructionIndex, | ||
messageDataOffset, | ||
messageDataSize: messageDataSize, | ||
messageInstructionIndex: instructionIndex, | ||
}, | ||
instructionData | ||
); | ||
|
||
return new TransactionInstruction({ | ||
keys: [], | ||
programId: Ed25519Program.programId, | ||
data: instructionData, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,93 +1,3 @@ | ||
import WebSocket from "isomorphic-ws"; | ||
|
||
import { | ||
BINARY_UPDATE_FORMAT_MAGIC, | ||
EVM_FORMAT_MAGIC, | ||
PARSED_FORMAT_MAGIC, | ||
type ParsedPayload, | ||
type Request, | ||
type Response, | ||
SOLANA_FORMAT_MAGIC_BE, | ||
} from "./protocol.js"; | ||
|
||
export type BinaryResponse = { | ||
subscriptionId: number; | ||
evm?: Buffer | undefined; | ||
solana?: Buffer | undefined; | ||
parsed?: ParsedPayload | undefined; | ||
}; | ||
export type JsonOrBinaryResponse = | ||
| { | ||
type: "json"; | ||
value: Response; | ||
} | ||
| { type: "binary"; value: BinaryResponse }; | ||
|
||
const UINT16_NUM_BYTES = 2; | ||
const UINT32_NUM_BYTES = 4; | ||
const UINT64_NUM_BYTES = 8; | ||
|
||
export class PythLazerClient { | ||
ws: WebSocket; | ||
|
||
constructor(url: string, token: string) { | ||
const finalUrl = new URL(url); | ||
finalUrl.searchParams.append("ACCESS_TOKEN", token); | ||
this.ws = new WebSocket(finalUrl); | ||
} | ||
|
||
addMessageListener(handler: (event: JsonOrBinaryResponse) => void) { | ||
this.ws.addEventListener("message", (event: WebSocket.MessageEvent) => { | ||
if (typeof event.data == "string") { | ||
handler({ | ||
type: "json", | ||
value: JSON.parse(event.data) as Response, | ||
}); | ||
} else if (Buffer.isBuffer(event.data)) { | ||
let pos = 0; | ||
const magic = event.data | ||
.subarray(pos, pos + UINT32_NUM_BYTES) | ||
.readUint32BE(); | ||
pos += UINT32_NUM_BYTES; | ||
if (magic != BINARY_UPDATE_FORMAT_MAGIC) { | ||
throw new Error("binary update format magic mismatch"); | ||
} | ||
// TODO: some uint64 values may not be representable as Number. | ||
const subscriptionId = Number( | ||
event.data.subarray(pos, pos + UINT64_NUM_BYTES).readBigInt64BE() | ||
); | ||
pos += UINT64_NUM_BYTES; | ||
|
||
const value: BinaryResponse = { subscriptionId }; | ||
while (pos < event.data.length) { | ||
const len = event.data | ||
.subarray(pos, pos + UINT16_NUM_BYTES) | ||
.readUint16BE(); | ||
pos += UINT16_NUM_BYTES; | ||
const magic = event.data | ||
.subarray(pos, pos + UINT32_NUM_BYTES) | ||
.readUint32BE(); | ||
if (magic == EVM_FORMAT_MAGIC) { | ||
value.evm = event.data.subarray(pos, pos + len); | ||
} else if (magic == SOLANA_FORMAT_MAGIC_BE) { | ||
value.solana = event.data.subarray(pos, pos + len); | ||
} else if (magic == PARSED_FORMAT_MAGIC) { | ||
value.parsed = JSON.parse( | ||
event.data.subarray(pos + UINT32_NUM_BYTES, pos + len).toString() | ||
) as ParsedPayload; | ||
} else { | ||
throw new Error("unknown magic: " + magic.toString()); | ||
} | ||
pos += len; | ||
} | ||
handler({ type: "binary", value }); | ||
} else { | ||
throw new TypeError("unexpected event data type"); | ||
} | ||
}); | ||
} | ||
|
||
send(request: Request) { | ||
this.ws.send(JSON.stringify(request)); | ||
} | ||
} | ||
export * from "./client.js"; | ||
export * from "./protocol.js"; | ||
export * from "./ed25519.js"; |
Oops, something went wrong.