Skip to content

Commit c2fd67d

Browse files
authored
[Solana]: Adds ability to transfer tokens to the feepayer (trustwallet#4503)
* Initial setup * Minor updates * Restructure
1 parent 793ae1c commit c2fd67d

File tree

3 files changed

+113
-12
lines changed

3 files changed

+113
-12
lines changed

rust/chains/tw_solana/src/modules/message_builder.rs

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ impl<'a> MessageBuilder<'a> {
160160
let transfer_ix = SystemInstructionBuilder::transfer(from, to, transfer.value)
161161
.with_references(references);
162162

163-
let mut builder = InstructionBuilder::default();
163+
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
164164
builder
165165
.maybe_advance_nonce(self.nonce_account()?, from)
166166
.maybe_priority_fee_price(self.priority_fee_price())
@@ -197,7 +197,7 @@ impl<'a> MessageBuilder<'a> {
197197
space: DEFAULT_SPACE,
198198
});
199199

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

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

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

244-
let mut builder = InstructionBuilder::default();
244+
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
245245
builder
246246
.maybe_advance_nonce(self.nonce_account()?, sender)
247247
.maybe_priority_fee_price(self.priority_fee_price())
@@ -269,7 +269,7 @@ impl<'a> MessageBuilder<'a> {
269269
custodian_account,
270270
);
271271

272-
let mut builder = InstructionBuilder::default();
272+
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
273273
builder
274274
.maybe_advance_nonce(self.nonce_account()?, sender)
275275
.maybe_priority_fee_price(self.priority_fee_price())
@@ -303,7 +303,7 @@ impl<'a> MessageBuilder<'a> {
303303
})
304304
.collect::<SigningResult<Vec<_>>>()?;
305305

306-
let mut builder = InstructionBuilder::default();
306+
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
307307
builder
308308
.maybe_advance_nonce(self.nonce_account()?, sender)
309309
.maybe_priority_fee_price(self.priority_fee_price())
@@ -337,7 +337,7 @@ impl<'a> MessageBuilder<'a> {
337337
token_address,
338338
match_program_id(create_token_acc.token_program_id),
339339
);
340-
let mut builder = InstructionBuilder::default();
340+
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
341341
builder
342342
.maybe_advance_nonce(self.nonce_account()?, funding_account)
343343
.maybe_priority_fee_price(self.priority_fee_price())
@@ -384,7 +384,7 @@ impl<'a> MessageBuilder<'a> {
384384
)
385385
.with_references(references);
386386

387-
let mut builder = InstructionBuilder::default();
387+
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
388388
builder
389389
.maybe_advance_nonce(self.nonce_account()?, signer)
390390
.maybe_priority_fee_price(self.priority_fee_price())
@@ -448,7 +448,7 @@ impl<'a> MessageBuilder<'a> {
448448
)
449449
.with_references(references);
450450

451-
let mut builder = InstructionBuilder::default();
451+
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
452452
builder
453453
.maybe_advance_nonce(self.nonce_account()?, signer)
454454
.maybe_priority_fee_price(self.priority_fee_price())
@@ -479,7 +479,7 @@ impl<'a> MessageBuilder<'a> {
479479
.context("Invalid nonce account")?
480480
};
481481

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

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

532-
let mut builder = InstructionBuilder::default();
532+
let mut builder = self.builder_with_token_transfer_to_fee_payer_if_applicable()?;
533533
builder
534534
.maybe_advance_nonce(Some(nonce_account), signer)
535535
.maybe_priority_fee_price(self.priority_fee_price())
@@ -580,6 +580,48 @@ impl<'a> MessageBuilder<'a> {
580580
self.signer_address()
581581
}
582582

583+
fn builder_with_token_transfer_to_fee_payer_if_applicable(
584+
&self,
585+
) -> SigningResult<InstructionBuilder> {
586+
let Some(sponsored_transfer_token) = self.input.token_transfer_to_fee_payer.as_ref() else {
587+
return Ok(InstructionBuilder::default());
588+
};
589+
let signer = self.signer_address()?;
590+
591+
let fee_mint_address =
592+
SolanaAddress::from_str(sponsored_transfer_token.fee_token_mint_address.as_ref())
593+
.into_tw()
594+
.context("Invalid fee mint address")?;
595+
596+
let sponsor_token_address =
597+
SolanaAddress::from_str(sponsored_transfer_token.fee_sponsor_token_address.as_ref())
598+
.into_tw()
599+
.context("Invalid sponsor token address")?;
600+
601+
let fee_sender_token_address =
602+
SolanaAddress::from_str(sponsored_transfer_token.fee_sender_token_address.as_ref())
603+
.into_tw()
604+
.context("Invalid fee sender token address")?;
605+
606+
let fee_decimals = sponsored_transfer_token
607+
.fee_decimals
608+
.try_into()
609+
.tw_err(SigningErrorType::Error_invalid_params)
610+
.context("Invalid fee decimals. Expected lower than 256")?;
611+
612+
let mut builder = InstructionBuilder::default();
613+
builder.add_instruction(TokenInstructionBuilder::transfer_checked(
614+
fee_sender_token_address,
615+
fee_mint_address,
616+
sponsor_token_address,
617+
signer,
618+
sponsored_transfer_token.fee_amount,
619+
fee_decimals,
620+
match_program_id(sponsored_transfer_token.fee_token_program_id),
621+
));
622+
Ok(builder)
623+
}
624+
583625
fn recent_blockhash(&self) -> SigningResult<Blockhash> {
584626
Blockhash::from_str(&self.input.recent_blockhash)
585627
.map_err(SigningError::from)

rust/tw_tests/tests/chains/solana/solana_sign.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,3 +973,39 @@ fn test_solana_sign_transfer_token_2022() {
973973
assert_eq!(output.encoded, "SAXNFUd7dNBu956Gi4XNuvMkKKjS9vp6puz45ErYMHFpMNwC3AQxDxGbweXt4GzY2FnUZ6ubm231NrdwWa8dg9bqgRMaHPLuPiy99YwtvcQ1E6mHxHqq8nL5VaN8wiVnrMU57zCLfHsSsVCHZc5peHHAPXMDE318uMCLLBwgDWuD1FfAvUAyXRSYniXzWG3jtBdDhuDohh13E2TMrtqTcKVv3crejFqFjtsNuW7KCqrZwxCv1ASNiiL2XScQBdHwStyjH2UTqLmT6wjGLiDYy7PZ88Tbz65r8NLr4Vb1aYSTChasfVjMLdybetfNaf4nJuBE4ZuXca7W66txKbHesxQbzrjUCXX12JFbKyaA8KJKBpbgkc9jWJjQkzyn");
974974
// https://explorer.solana.com/tx/Lg1xWzsC9GatQMu1ZXv23t7snC92RRvbKJe22bsS76GUb8C8a9q3HPkiUnFoK6AWKSoNSsmko1EBnvKkCnL8b7w?cluster=devnet
975975
}
976+
977+
#[test]
978+
fn test_solana_sign_sponsored_transfer_token_with_external_fee_payer() {
979+
let token_transfer_to_fee_payer = Proto::TokenTransferToFeePayer {
980+
fee_token_mint_address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".into(),
981+
fee_sponsor_token_address: "reqtRKoVXCKVtvCL47VBxwncifQE8oJL6ZUzo1a28hD".into(),
982+
fee_sender_token_address: "5Wa3KnBQAGs2KKZvHGZ7TcWJHexKCgG3F2H6RFMars67".into(),
983+
fee_amount: 1,
984+
fee_decimals: 6,
985+
..Proto::TokenTransferToFeePayer::default()
986+
};
987+
988+
let create_transfer_token = Proto::TokenTransfer {
989+
token_mint_address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".into(),
990+
recipient_token_address: "8CFJPb4MNuUXCn3nmau2VSKY4RyvRhd8796GQgvchdtW".into(),
991+
sender_token_address: "5Wa3KnBQAGs2KKZvHGZ7TcWJHexKCgG3F2H6RFMars67".into(),
992+
amount: 1,
993+
decimals: 6,
994+
..Proto::TokenTransfer::default()
995+
};
996+
let input = Proto::SigningInput {
997+
private_key: b58("9YtuoD4sH4h88CVM8DSnkfoAaLY7YeGC2TarDJ8eyMS5"),
998+
recent_blockhash: "Csc3aso6RQS9qav9aCrpCAyLiQXnyGDL1ZymcH1SE9pU".into(),
999+
transaction_type: TransactionType::token_transfer_transaction(create_transfer_token),
1000+
fee_payer_private_key: b58("66ApBuKpo2uSzpjGBraHq7HP8UZMUJzp3um8FdEjkC9c"),
1001+
token_transfer_to_fee_payer: Some(token_transfer_to_fee_payer),
1002+
..Proto::SigningInput::default()
1003+
};
1004+
1005+
let mut signer = AnySignerHelper::<Proto::SigningOutput>::default();
1006+
let output = signer.sign(CoinType::Solana, input);
1007+
1008+
assert_eq!(output.error, SigningError::OK);
1009+
// https://solscan.io/tx/3z7beuRPcr6WmTRvCDNgSNXBaEUTAy8EYHN93eUiLXoFoj2VbWuPbvf7nQoZxTHbG6ChQuTJDqwaQnUzK4WxYaQA
1010+
assert_eq!(output.encoded, "gnSfLvpTeWGFvEKGDNAwQpQYczANiAHcpj4ghRgKsJfTXJjqaGYnNG2Ay2JwR5XdRvdkeLjHdht7VctoJkxDcYLRNjWmFcb3khwZqV4oRcU3HCxqnjGbiFmBCTjsupUt4ZzsJs8DS9WGHPgQGfRVQdmq1Zv6Kd4KDR88aT3uLmdNsu1XP5Es5SFAqByGnwAnkthDfNvcmpW9iAsZdf4v7gTsgFZV14ZfsNh66TGzVJLepz689D4jKb19AyvPwBPYYsvpRLxeEaa3zJvsdBBoVkWMZzC2Y8oqxoPXCRXnxzKX9gJSew1P2bgZDN3j3BvFQ19zTYsdugGtRetV94yQgx5xh8Vk9Asbj3YCmEZpFMZborqeanvgK2mWs2rQmbanMY6Fi6FB1xN24YN2B38pK2g3DCYp6nNh1ueacrDakbyrRFCpyKo26yqrkqnbbKZ9roAgvrvm5zhju2GhWU5t5cPc4ADfZbfRWaV2ojETv1a9W838MB7h4N5a97kgkdnuuR5A4fJr5K4jizC2rNeLciDoZQuzoNE3TpnYqxpnJPQWQQB1vGHqdXTTiDc47i7kLm");
1011+
}

src/proto/Solana.proto

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,27 @@ message CreateAndTransferToken {
146146
TokenProgramId token_program_id = 9;
147147
}
148148

149+
// Transfer tokens to the feepayer
150+
message TokenTransferToFeePayer {
151+
// Mint address of the fee token
152+
string fee_token_mint_address = 1;
153+
154+
// Token account address of the fee sponsor for the fee mint
155+
string fee_sponsor_token_address = 2;
156+
157+
// Fee amount
158+
uint64 fee_amount = 3;
159+
160+
// Sender's fee token address
161+
string fee_sender_token_address = 4;
162+
163+
// Note: 8-bit value
164+
uint32 fee_decimals = 5;
165+
166+
// optional token program id
167+
TokenProgramId fee_token_program_id = 6;
168+
}
169+
149170
message CreateNonceAccount {
150171
// Required for building pre-signing hash of a transaction
151172
string nonce_account = 1;
@@ -286,6 +307,8 @@ message SigningInput {
286307
// fee for higher transaction prioritization.
287308
// https://solana.com/docs/intro/transaction_fees#prioritization-fee
288309
PriorityFeeLimit priority_fee_limit = 23;
310+
// Optional token transfer to fee payer
311+
TokenTransferToFeePayer token_transfer_to_fee_payer = 24;
289312
}
290313

291314
// Result containing the signed and encoded transaction.

0 commit comments

Comments
 (0)