Skip to content

Commit af0c725

Browse files
authored
Merge pull request #160 from BitGo/BTC-3025-sol-wasm-staking-fixes
feat(wasm-solana): complete Jito stake/unstake and sign generated key…
2 parents 24a1299 + 4ee3d1d commit af0c725

File tree

10 files changed

+353
-86
lines changed

10 files changed

+353
-86
lines changed

packages/wasm-solana/js/intentBuilder.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,11 @@ export function buildFromIntent(
267267
): BuildFromIntentResult {
268268
const result = IntentNamespace.build_from_intent(intent, params) as WasmBuildResult;
269269

270+
// Generated keypair signing happens in Rust (build_from_intent signs
271+
// before returning). Signatures survive the wasm-bindgen boundary
272+
// because WasmTransaction is a heap-allocated Rust object that JS
273+
// holds a handle to — no serialization round-trip.
274+
270275
return {
271276
transaction: Transaction.fromWasm(result.transaction),
272277
generatedKeypairs: result.generatedKeypairs,

packages/wasm-solana/js/keypair.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ export class Keypair {
6969
return this._wasm.to_base58();
7070
}
7171

72+
/**
73+
* Sign a message with this keypair
74+
* @param message - The message bytes to sign
75+
* @returns The 64-byte Ed25519 signature
76+
*/
77+
sign(message: Uint8Array): Uint8Array {
78+
return this._wasm.sign(message);
79+
}
80+
7281
/**
7382
* Get the underlying WASM instance (internal use only)
7483
* @internal

packages/wasm-solana/js/transaction.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,18 @@ export class Transaction {
224224
return idx ?? null;
225225
}
226226

227+
/**
228+
* Sign this transaction with a base58-encoded Ed25519 secret key.
229+
*
230+
* Derives the public key from the secret, signs the transaction message,
231+
* and places the signature at the correct signer index.
232+
*
233+
* @param secretKeyBase58 - The Ed25519 secret key (32-byte seed) as base58
234+
*/
235+
signWithSecretKey(secretKeyBase58: string): void {
236+
this._wasm.sign_with_secret_key(secretKeyBase58);
237+
}
238+
227239
/**
228240
* Get the underlying WASM instance (internal use only)
229241
* @internal

packages/wasm-solana/src/instructions/decode.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Instruction decoding using official Solana interface crates.
22
33
use super::types::*;
4+
use crate::intent::AuthorizeType;
45
use solana_compute_budget_interface::ComputeBudgetInstruction;
56
use solana_stake_interface::instruction::StakeInstruction;
67
use solana_system_interface::instruction::SystemInstruction;
@@ -155,8 +156,10 @@ fn decode_stake_instruction(ctx: InstructionContext) -> ParsedInstruction {
155156
// Accounts: [0] stake, [1] clock, [2] authority, [3] optional custodian
156157
if ctx.accounts.len() >= 3 {
157158
let auth_type = match stake_authorize {
158-
solana_stake_interface::state::StakeAuthorize::Staker => "Staker",
159-
solana_stake_interface::state::StakeAuthorize::Withdrawer => "Withdrawer",
159+
solana_stake_interface::state::StakeAuthorize::Staker => AuthorizeType::Staker,
160+
solana_stake_interface::state::StakeAuthorize::Withdrawer => {
161+
AuthorizeType::Withdrawer
162+
}
160163
};
161164
let custodian = if ctx.accounts.len() >= 4 {
162165
Some(ctx.accounts[3].clone())
@@ -167,7 +170,7 @@ fn decode_stake_instruction(ctx: InstructionContext) -> ParsedInstruction {
167170
staking_address: ctx.accounts[0].clone(),
168171
old_authorize_address: ctx.accounts[2].clone(),
169172
new_authorize_address: new_authority.to_string(),
170-
authorize_type: auth_type.to_string(),
173+
authorize_type: auth_type,
171174
custodian_address: custodian,
172175
})
173176
} else {
@@ -178,8 +181,10 @@ fn decode_stake_instruction(ctx: InstructionContext) -> ParsedInstruction {
178181
// Accounts: [0] stake, [1] clock, [2] authority, [3] new_authority (signer), [4] optional custodian
179182
if ctx.accounts.len() >= 4 {
180183
let auth_type = match stake_authorize {
181-
solana_stake_interface::state::StakeAuthorize::Staker => "Staker",
182-
solana_stake_interface::state::StakeAuthorize::Withdrawer => "Withdrawer",
184+
solana_stake_interface::state::StakeAuthorize::Staker => AuthorizeType::Staker,
185+
solana_stake_interface::state::StakeAuthorize::Withdrawer => {
186+
AuthorizeType::Withdrawer
187+
}
183188
};
184189
let custodian = if ctx.accounts.len() >= 5 {
185190
Some(ctx.accounts[4].clone())
@@ -190,7 +195,7 @@ fn decode_stake_instruction(ctx: InstructionContext) -> ParsedInstruction {
190195
staking_address: ctx.accounts[0].clone(),
191196
old_authorize_address: ctx.accounts[2].clone(),
192197
new_authorize_address: ctx.accounts[3].clone(),
193-
authorize_type: auth_type.to_string(),
198+
authorize_type: auth_type,
194199
custodian_address: custodian,
195200
})
196201
} else {

packages/wasm-solana/src/instructions/try_into_js_value.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,43 @@ use base64::prelude::*;
99
use wasm_bindgen::JsValue;
1010

1111
use super::types::*;
12+
use crate::intent::{AuthorizeType, KeypairPurpose, StakingType};
13+
14+
// =============================================================================
15+
// Enum → JS string conversions
16+
// =============================================================================
17+
18+
impl TryIntoJsValue for StakingType {
19+
fn try_to_js_value(&self) -> Result<JsValue, JsConversionError> {
20+
let s = match self {
21+
StakingType::Native => "NATIVE",
22+
StakingType::Jito => "JITO",
23+
StakingType::Marinade => "MARINADE",
24+
};
25+
Ok(JsValue::from_str(s))
26+
}
27+
}
28+
29+
impl TryIntoJsValue for AuthorizeType {
30+
fn try_to_js_value(&self) -> Result<JsValue, JsConversionError> {
31+
let s = match self {
32+
AuthorizeType::Staker => "Staker",
33+
AuthorizeType::Withdrawer => "Withdrawer",
34+
};
35+
Ok(JsValue::from_str(s))
36+
}
37+
}
38+
39+
impl TryIntoJsValue for KeypairPurpose {
40+
fn try_to_js_value(&self) -> Result<JsValue, JsConversionError> {
41+
let s = match self {
42+
KeypairPurpose::StakeAccount => "stakeAccount",
43+
KeypairPurpose::UnstakeAccount => "unstakeAccount",
44+
KeypairPurpose::TransferAuthority => "transferAuthority",
45+
};
46+
Ok(JsValue::from_str(s))
47+
}
48+
}
1249

1350
// =============================================================================
1451
// System Program Params

packages/wasm-solana/src/instructions/types.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ pub struct StakingActivateParams {
124124
pub staking_address: String,
125125
pub amount: u64,
126126
pub validator: String,
127-
pub staking_type: String, // "NATIVE", "JITO", "MARINADE"
127+
pub staking_type: crate::intent::StakingType,
128128
}
129129

130130
#[derive(Debug, Clone)]
@@ -152,7 +152,7 @@ pub struct StakingAuthorizeParams {
152152
pub staking_address: String,
153153
pub old_authorize_address: String,
154154
pub new_authorize_address: String,
155-
pub authorize_type: String, // "Staker" or "Withdrawer"
155+
pub authorize_type: crate::intent::AuthorizeType,
156156
pub custodian_address: Option<String>,
157157
}
158158

0 commit comments

Comments
 (0)