diff --git a/packages/wasm-solana/js/transaction.ts b/packages/wasm-solana/js/transaction.ts index 32e8f86..8e3ec4f 100644 --- a/packages/wasm-solana/js/transaction.ts +++ b/packages/wasm-solana/js/transaction.ts @@ -87,6 +87,23 @@ export class Transaction { return this._wasm.num_signatures; } + /** + * Get the transaction ID (first signature as base58). + * + * For Solana, the transaction ID is the first signature. + * Returns "UNSIGNED" if the transaction has no valid signatures. + * + * @example + * ```typescript + * const tx = Transaction.fromBytes(txBytes); + * tx.addSignature(pubkey, signature); + * console.log(tx.id); // Base58 encoded signature + * ``` + */ + get id(): string { + return this._wasm.id; + } + /** * Get the signable message payload (what gets signed) * This is the serialized message that signers sign @@ -114,6 +131,14 @@ export class Transaction { return this._wasm.to_bytes(); } + /** + * Serialize to network broadcast format. + * @returns The transaction as bytes ready for broadcast + */ + toBroadcastFormat(): Uint8Array { + return this.toBytes(); + } + /** * Get all account keys as Pubkey instances * @returns Array of account public keys diff --git a/packages/wasm-solana/js/versioned.ts b/packages/wasm-solana/js/versioned.ts index 82c695e..1df22a2 100644 --- a/packages/wasm-solana/js/versioned.ts +++ b/packages/wasm-solana/js/versioned.ts @@ -168,6 +168,16 @@ export class VersionedTransaction { return this.inner.num_signatures; } + /** + * Get the transaction ID (first signature as base58). + * + * For Solana, the transaction ID is the first signature. + * Returns "UNSIGNED" if the transaction has no valid signatures. + */ + get id(): string { + return this.inner.id; + } + /** * Get the signable message payload. */ @@ -199,6 +209,14 @@ export class VersionedTransaction { return Buffer.from(this.toBytes()).toString("base64"); } + /** + * Serialize to network broadcast format. + * @returns The transaction as bytes ready for broadcast + */ + toBroadcastFormat(): Uint8Array { + return this.toBytes(); + } + /** * Get static account keys (accounts stored directly in the message). * For versioned transactions, additional accounts may be referenced via ALTs. diff --git a/packages/wasm-solana/src/wasm/transaction.rs b/packages/wasm-solana/src/wasm/transaction.rs index 803e688..86a0532 100644 --- a/packages/wasm-solana/src/wasm/transaction.rs +++ b/packages/wasm-solana/src/wasm/transaction.rs @@ -9,6 +9,7 @@ use crate::error::WasmSolanaError; use crate::transaction::{Transaction, TransactionExt}; use crate::versioned::{detect_transaction_version, TxVersion, VersionedTransactionExt}; use solana_message::VersionedMessage; +use solana_sdk::bs58; use solana_transaction::versioned::VersionedTransaction; use wasm_bindgen::prelude::*; @@ -56,6 +57,24 @@ impl WasmTransaction { self.inner.num_signatures() } + /// Get the transaction ID (first signature as base58). + /// + /// For Solana, the transaction ID is the first signature. + /// Returns "UNSIGNED" if the first signature is all zeros (unsigned transaction). + #[wasm_bindgen(getter)] + pub fn id(&self) -> String { + if let Some(sig) = self.inner.signatures.first() { + let bytes: &[u8] = sig.as_ref(); + // Check if signature is all zeros (unsigned) + if bytes.iter().all(|&b| b == 0) { + return "UNSIGNED".to_string(); + } + bs58::encode(bytes).into_string() + } else { + "UNSIGNED".to_string() + } + } + /// Get the signable message payload (what gets signed). /// /// This is the serialized message that signers sign. @@ -242,6 +261,24 @@ impl WasmVersionedTransaction { self.inner.num_signatures() } + /// Get the transaction ID (first signature as base58). + /// + /// For Solana, the transaction ID is the first signature. + /// Returns "UNSIGNED" if the first signature is all zeros (unsigned transaction). + #[wasm_bindgen(getter)] + pub fn id(&self) -> String { + if let Some(sig) = self.inner.signatures.first() { + let bytes: &[u8] = sig.as_ref(); + // Check if signature is all zeros (unsigned) + if bytes.iter().all(|&b| b == 0) { + return "UNSIGNED".to_string(); + } + bs58::encode(bytes).into_string() + } else { + "UNSIGNED".to_string() + } + } + /// Get the signable message payload. #[wasm_bindgen] pub fn signable_payload(&self) -> js_sys::Uint8Array { diff --git a/packages/wasm-solana/test/transaction.ts b/packages/wasm-solana/test/transaction.ts index 768ed48..4964ef6 100644 --- a/packages/wasm-solana/test/transaction.ts +++ b/packages/wasm-solana/test/transaction.ts @@ -126,6 +126,29 @@ describe("Transaction", () => { assert.strictEqual(instr.programId, "11111111111111111111111111111111"); }); + describe("id getter", () => { + it("should return UNSIGNED for unsigned transaction", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + // The test transaction has an all-zeros signature (unsigned) + assert.strictEqual(tx.id, "UNSIGNED"); + }); + + it("should return base58 signature after signing", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const feePayer = tx.feePayer; + + // Add a non-zero signature + const signature = new Uint8Array(64); + for (let i = 0; i < 64; i++) signature[i] = i + 1; + tx.addSignature(feePayer, signature); + + // ID should now be a base58-encoded string of the signature + const id = tx.id; + assert.notStrictEqual(id, "UNSIGNED"); + assert.ok(id.length > 20); // base58 encoded 64 bytes should be ~80+ chars + }); + }); + describe("signerIndex", () => { it("should return signer index for fee payer", () => { const tx = Transaction.fromBytes(TEST_TX_BYTES); diff --git a/packages/wasm-solana/test/versioned.ts b/packages/wasm-solana/test/versioned.ts index 085f08c..b9e9443 100644 --- a/packages/wasm-solana/test/versioned.ts +++ b/packages/wasm-solana/test/versioned.ts @@ -92,4 +92,29 @@ describe("VersionedTransaction", () => { assert.strictEqual(tx.feePayer, tx2.feePayer); }); }); + + describe("id getter", () => { + it("should return UNSIGNED for unsigned transaction", () => { + const bytes = Buffer.from(LEGACY_TX_BASE64, "base64"); + const tx = VersionedTransaction.fromBytes(bytes); + // The test transaction has an all-zeros signature (unsigned) + assert.strictEqual(tx.id, "UNSIGNED"); + }); + + it("should return base58 signature after signing", () => { + const bytes = Buffer.from(LEGACY_TX_BASE64, "base64"); + const tx = VersionedTransaction.fromBytes(bytes); + const feePayer = tx.feePayer; + + // Add a non-zero signature + const signature = new Uint8Array(64); + for (let i = 0; i < 64; i++) signature[i] = i + 1; + tx.addSignature(feePayer, signature); + + // ID should now be a base58-encoded string of the signature + const id = tx.id; + assert.notStrictEqual(id, "UNSIGNED"); + assert.ok(id.length > 20); // base58 encoded 64 bytes should be ~80+ chars + }); + }); });