Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 54 additions & 12 deletions rust/chains/tw_solana/src/modules/message_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ impl<'a> MessageBuilder<'a> {
let transfer_ix = SystemInstructionBuilder::transfer(from, to, transfer.value)
.with_references(references);

let mut builder = InstructionBuilder::default();
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
builder
.maybe_advance_nonce(self.nonce_account()?, from)
.maybe_priority_fee_price(self.priority_fee_price())
Expand Down Expand Up @@ -197,7 +197,7 @@ impl<'a> MessageBuilder<'a> {
space: DEFAULT_SPACE,
});

let mut builder = InstructionBuilder::default();
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
builder
.maybe_advance_nonce(self.nonce_account()?, sender)
.maybe_priority_fee_price(self.priority_fee_price())
Expand All @@ -217,7 +217,7 @@ impl<'a> MessageBuilder<'a> {

let deactivate_ix = StakeInstructionBuilder::deactivate(stake_account, sender);

let mut builder = InstructionBuilder::default();
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
builder
.maybe_advance_nonce(self.nonce_account()?, sender)
.maybe_priority_fee_price(self.priority_fee_price())
Expand All @@ -241,7 +241,7 @@ impl<'a> MessageBuilder<'a> {
.collect::<SigningResult<Vec<_>>>()
.context("Invalid stake account(s)")?;

let mut builder = InstructionBuilder::default();
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
builder
.maybe_advance_nonce(self.nonce_account()?, sender)
.maybe_priority_fee_price(self.priority_fee_price())
Expand Down Expand Up @@ -269,7 +269,7 @@ impl<'a> MessageBuilder<'a> {
custodian_account,
);

let mut builder = InstructionBuilder::default();
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
builder
.maybe_advance_nonce(self.nonce_account()?, sender)
.maybe_priority_fee_price(self.priority_fee_price())
Expand Down Expand Up @@ -303,7 +303,7 @@ impl<'a> MessageBuilder<'a> {
})
.collect::<SigningResult<Vec<_>>>()?;

let mut builder = InstructionBuilder::default();
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
builder
.maybe_advance_nonce(self.nonce_account()?, sender)
.maybe_priority_fee_price(self.priority_fee_price())
Expand Down Expand Up @@ -337,7 +337,7 @@ impl<'a> MessageBuilder<'a> {
token_address,
match_program_id(create_token_acc.token_program_id),
);
let mut builder = InstructionBuilder::default();
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
builder
.maybe_advance_nonce(self.nonce_account()?, funding_account)
.maybe_priority_fee_price(self.priority_fee_price())
Expand Down Expand Up @@ -384,7 +384,7 @@ impl<'a> MessageBuilder<'a> {
)
.with_references(references);

let mut builder = InstructionBuilder::default();
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
builder
.maybe_advance_nonce(self.nonce_account()?, signer)
.maybe_priority_fee_price(self.priority_fee_price())
Expand Down Expand Up @@ -448,7 +448,7 @@ impl<'a> MessageBuilder<'a> {
)
.with_references(references);

let mut builder = InstructionBuilder::default();
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
builder
.maybe_advance_nonce(self.nonce_account()?, signer)
.maybe_priority_fee_price(self.priority_fee_price())
Expand Down Expand Up @@ -479,7 +479,7 @@ impl<'a> MessageBuilder<'a> {
.context("Invalid nonce account")?
};

let mut builder = InstructionBuilder::default();
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
builder
.maybe_advance_nonce(prev_nonce_account, signer)
.maybe_priority_fee_price(self.priority_fee_price())
Expand All @@ -506,7 +506,7 @@ impl<'a> MessageBuilder<'a> {
.into_tw()
.context("Invalid recipient")?;

let mut builder = InstructionBuilder::default();
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
builder
.maybe_advance_nonce(self.nonce_account()?, signer)
.maybe_priority_fee_price(self.priority_fee_price())
Expand All @@ -529,7 +529,7 @@ impl<'a> MessageBuilder<'a> {
.into_tw()
.context("Invalid nonce account")?;

let mut builder = InstructionBuilder::default();
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
builder
.maybe_advance_nonce(Some(nonce_account), signer)
.maybe_priority_fee_price(self.priority_fee_price())
Expand Down Expand Up @@ -580,6 +580,48 @@ impl<'a> MessageBuilder<'a> {
self.signer_address()
}

fn builder_with_token_transfer_to_fee_payer_if_applicable(
&self,
) -> SigningResult<InstructionBuilder> {
let Some(sponsored_transfer_token) = self.input.token_transfer_to_fee_payer.as_ref() else {
return Ok(InstructionBuilder::default());
};
let signer = self.signer_address()?;

let fee_mint_address =
SolanaAddress::from_str(sponsored_transfer_token.fee_token_mint_address.as_ref())
.into_tw()
.context("Invalid fee mint address")?;

let sponsor_token_address =
SolanaAddress::from_str(sponsored_transfer_token.fee_sponsor_token_address.as_ref())
.into_tw()
.context("Invalid sponsor token address")?;

let fee_sender_token_address =
SolanaAddress::from_str(sponsored_transfer_token.fee_sender_token_address.as_ref())
.into_tw()
.context("Invalid fee sender token address")?;

let fee_decimals = sponsored_transfer_token
.fee_decimals
.try_into()
.tw_err(SigningErrorType::Error_invalid_params)
.context("Invalid fee decimals. Expected lower than 256")?;

let mut builder = InstructionBuilder::default();
builder.add_instruction(TokenInstructionBuilder::transfer_checked(
fee_sender_token_address,
fee_mint_address,
sponsor_token_address,
signer,
sponsored_transfer_token.fee_amount,
fee_decimals,
match_program_id(sponsored_transfer_token.fee_token_program_id),
));
Ok(builder)
}

fn recent_blockhash(&self) -> SigningResult<Blockhash> {
Blockhash::from_str(&self.input.recent_blockhash)
.map_err(SigningError::from)
Expand Down
36 changes: 36 additions & 0 deletions rust/tw_tests/tests/chains/solana/solana_sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -973,3 +973,39 @@ fn test_solana_sign_transfer_token_2022() {
assert_eq!(output.encoded, "SAXNFUd7dNBu956Gi4XNuvMkKKjS9vp6puz45ErYMHFpMNwC3AQxDxGbweXt4GzY2FnUZ6ubm231NrdwWa8dg9bqgRMaHPLuPiy99YwtvcQ1E6mHxHqq8nL5VaN8wiVnrMU57zCLfHsSsVCHZc5peHHAPXMDE318uMCLLBwgDWuD1FfAvUAyXRSYniXzWG3jtBdDhuDohh13E2TMrtqTcKVv3crejFqFjtsNuW7KCqrZwxCv1ASNiiL2XScQBdHwStyjH2UTqLmT6wjGLiDYy7PZ88Tbz65r8NLr4Vb1aYSTChasfVjMLdybetfNaf4nJuBE4ZuXca7W66txKbHesxQbzrjUCXX12JFbKyaA8KJKBpbgkc9jWJjQkzyn");
// https://explorer.solana.com/tx/Lg1xWzsC9GatQMu1ZXv23t7snC92RRvbKJe22bsS76GUb8C8a9q3HPkiUnFoK6AWKSoNSsmko1EBnvKkCnL8b7w?cluster=devnet
}

#[test]
fn test_solana_sign_sponsored_transfer_token_with_external_fee_payer() {
let token_transfer_to_fee_payer = Proto::TokenTransferToFeePayer {
fee_token_mint_address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".into(),
fee_sponsor_token_address: "reqtRKoVXCKVtvCL47VBxwncifQE8oJL6ZUzo1a28hD".into(),
fee_sender_token_address: "5Wa3KnBQAGs2KKZvHGZ7TcWJHexKCgG3F2H6RFMars67".into(),
fee_amount: 1,
fee_decimals: 6,
..Proto::TokenTransferToFeePayer::default()
};

let create_transfer_token = Proto::TokenTransfer {
token_mint_address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".into(),
recipient_token_address: "8CFJPb4MNuUXCn3nmau2VSKY4RyvRhd8796GQgvchdtW".into(),
sender_token_address: "5Wa3KnBQAGs2KKZvHGZ7TcWJHexKCgG3F2H6RFMars67".into(),
amount: 1,
decimals: 6,
..Proto::TokenTransfer::default()
};
let input = Proto::SigningInput {
private_key: b58("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"),
recent_blockhash: "Csc3aso6RQS9qav9aCrpCAyLiQXnyGDL1ZymcH1SE9pU".into(),
transaction_type: TransactionType::token_transfer_transaction(create_transfer_token),
fee_payer_private_key: b58("66ApBuKpo2uSzpjGBraHq7HP8UZMUJzp3um8FdEjkC9c"),
token_transfer_to_fee_payer: Some(token_transfer_to_fee_payer),
..Proto::SigningInput::default()
};

let mut signer = AnySignerHelper::<Proto::SigningOutput>::default();
let output = signer.sign(CoinType::Solana, input);

assert_eq!(output.error, SigningError::OK);
// https://solscan.io/tx/3z7beuRPcr6WmTRvCDNgSNXBaEUTAy8EYHN93eUiLXoFoj2VbWuPbvf7nQoZxTHbG6ChQuTJDqwaQnUzK4WxYaQA
assert_eq!(output.encoded, "gnSfLvpTeWGFvEKGDNAwQpQYczANiAHcpj4ghRgKsJfTXJjqaGYnNG2Ay2JwR5XdRvdkeLjHdht7VctoJkxDcYLRNjWmFcb3khwZqV4oRcU3HCxqnjGbiFmBCTjsupUt4ZzsJs8DS9WGHPgQGfRVQdmq1Zv6Kd4KDR88aT3uLmdNsu1XP5Es5SFAqByGnwAnkthDfNvcmpW9iAsZdf4v7gTsgFZV14ZfsNh66TGzVJLepz689D4jKb19AyvPwBPYYsvpRLxeEaa3zJvsdBBoVkWMZzC2Y8oqxoPXCRXnxzKX9gJSew1P2bgZDN3j3BvFQ19zTYsdugGtRetV94yQgx5xh8Vk9Asbj3YCmEZpFMZborqeanvgK2mWs2rQmbanMY6Fi6FB1xN24YN2B38pK2g3DCYp6nNh1ueacrDakbyrRFCpyKo26yqrkqnbbKZ9roAgvrvm5zhju2GhWU5t5cPc4ADfZbfRWaV2ojETv1a9W838MB7h4N5a97kgkdnuuR5A4fJr5K4jizC2rNeLciDoZQuzoNE3TpnYqxpnJPQWQQB1vGHqdXTTiDc47i7kLm");
}
23 changes: 23 additions & 0 deletions src/proto/Solana.proto
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,27 @@ message CreateAndTransferToken {
TokenProgramId token_program_id = 9;
}

// Transfer tokens to the feepayer
message TokenTransferToFeePayer {
// Mint address of the fee token
string fee_token_mint_address = 1;

// Token account address of the fee sponsor for the fee mint
string fee_sponsor_token_address = 2;

// Fee amount
uint64 fee_amount = 3;

// Sender's fee token address
string fee_sender_token_address = 4;

// Note: 8-bit value
uint32 fee_decimals = 5;

// optional token program id
TokenProgramId fee_token_program_id = 6;
}

message CreateNonceAccount {
// Required for building pre-signing hash of a transaction
string nonce_account = 1;
Expand Down Expand Up @@ -286,6 +307,8 @@ message SigningInput {
// fee for higher transaction prioritization.
// https://solana.com/docs/intro/transaction_fees#prioritization-fee
PriorityFeeLimit priority_fee_limit = 23;
// Optional token transfer to fee payer
TokenTransferToFeePayer token_transfer_to_fee_payer = 24;
}

// Result containing the signed and encoded transaction.
Expand Down
Loading