Skip to content

Commit

Permalink
[Aptos]: Add support for fungible assets (#4206)
Browse files Browse the repository at this point in the history
* [Aptos]: Add support for fungible assets

* Addresses review comments

* Minor fix

---------

Co-authored-by: Sergei Boiko <[email protected]>
  • Loading branch information
gupnik and satoshiotomakan authored Jan 10, 2025
1 parent c764e15 commit 6bae053
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,41 @@ class TestAptosSigner {
"1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf2502000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e7472616e736665725f636f696e730107e9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9086d65655f636f696e074d6565436f696e000220b7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c081027000000000000d0070000000000006400000000000000c2276ada0000000001002062e7a6a486553b56a53e89dfae3f780693e537e5b0a7ed33290780e581ca83694030ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d"
)
}

@Test
fun AptosFungibleAssetTransfer() {
// Successfully broadcasted https://explorer.aptoslabs.com/txn/0x475fc97bcba87907166a720676e1b2f5320e613fd13014df37dcf17b09ff0e98/balanceChange?network=mainnet
val key =
"5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec".toHexBytesInByteString()

val fungibleAssetTransferMessage = Aptos.FungibleAssetTransferMessage.newBuilder()
.setAmount(100000000)
.setTo("0x2d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e52")
.setMetadataAddress("0x2ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12")
.build()
val signingInput = Aptos.SigningInput.newBuilder()
.setChainId(1)
.setSender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30")
.setSequenceNumber(74)
.setGasUnitPrice(100)
.setMaxGasAmount(20)
.setExpirationTimestampSecs(1736060099)
.setFungibleAssetTransfer(fungibleAssetTransferMessage)
.setPrivateKey(key)
.build()

val result = AnySigner.sign(signingInput, CoinType.APTOS, Aptos.SigningOutput.parser())
assertEquals(
Numeric.cleanHexPrefix(Numeric.toHexString(result.rawTxn.toByteArray())),
"07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a670000000001"
)
assertEquals(
Numeric.cleanHexPrefix(Numeric.toHexString(result.authenticator.signature.toByteArray())),
"2d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c"
)
assertEquals(
Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())),
"07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a6700000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c402d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c"
)
}
}
31 changes: 31 additions & 0 deletions rust/chains/tw_aptos/src/aptos_move_packages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
//
// Copyright © 2017 Trust Wallet.

use std::str::FromStr;

use crate::transaction_payload::{EntryFunction, TransactionPayload};
use move_core_types::account_address::AccountAddress;
use move_core_types::ident_str;
Expand Down Expand Up @@ -205,3 +207,32 @@ pub fn managed_coin_register(coin_type: TypeTag) -> TransactionPayload {
json!([]),
))
}

pub fn fungible_asset_transfer(
metadata_address: AccountAddress,
to: AccountAddress,
amount: u64,
) -> SigningResult<TransactionPayload> {
Ok(TransactionPayload::EntryFunction(EntryFunction::new(
ModuleId::new(
AccountAddress::new([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1,
]),
ident_str!("primary_fungible_store").to_owned(),
),
ident_str!("transfer").to_owned(),
vec![TypeTag::from_str("0x1::fungible_asset::Metadata")
.tw_err(|_| SigningErrorType::Error_internal)?],
vec![
bcs::encode(&metadata_address)?,
bcs::encode(&to)?,
bcs::encode(&amount)?,
],
json!([
metadata_address.to_hex_literal(),
to.to_hex_literal(),
amount.to_string()
]),
)))
}
26 changes: 24 additions & 2 deletions rust/chains/tw_aptos/src/transaction_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
use crate::address::from_account_error;
use crate::aptos_move_packages::{
aptos_account_create_account, aptos_account_transfer, aptos_account_transfer_coins,
coin_transfer, managed_coin_register, token_transfers_cancel_offer_script,
token_transfers_claim_script, token_transfers_offer_script,
coin_transfer, fungible_asset_transfer, managed_coin_register,
token_transfers_cancel_offer_script, token_transfers_claim_script,
token_transfers_offer_script,
};
use crate::constants::{GAS_UNIT_PRICE, MAX_GAS_AMOUNT};
use crate::liquid_staking::{
Expand Down Expand Up @@ -147,6 +148,18 @@ impl TransactionFactory {
convert_proto_struct_tag_to_type_tag(func)?,
)
},
OneOftransaction_payload::fungible_asset_transfer(fungible_asset_transfer) => factory
.fungible_asset_transfer(
AccountAddress::from_str(&fungible_asset_transfer.metadata_address)
.map_err(from_account_error)
.into_tw()
.context("Invalid metadata address")?,
AccountAddress::from_str(&fungible_asset_transfer.to)
.map_err(from_account_error)
.into_tw()
.context("Invalid destination address")?,
fungible_asset_transfer.amount,
),
OneOftransaction_payload::None => {
let is_blind_sign = !input.any_encoded.is_empty();
let v = serde_json::from_str::<Value>(&input.any_encoded)
Expand Down Expand Up @@ -253,6 +266,15 @@ impl TransactionFactory {
Ok(self.payload(coin_transfer(coin_type, to, amount)?))
}

pub fn fungible_asset_transfer(
&self,
metadata_address: AccountAddress,
to: AccountAddress,
amount: u64,
) -> SigningResult<TransactionBuilder> {
Ok(self.payload(fungible_asset_transfer(metadata_address, to, amount)?))
}

pub fn implicitly_create_user_and_coins_transfer(
&self,
to: AccountAddress,
Expand Down
67 changes: 67 additions & 0 deletions rust/chains/tw_aptos/tests/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ pub struct TokenTransfer {
tag: TypeTag,
}

pub struct FungibleAssetTransfer {
metadata_address: String,
to: String,
amount: u64,
}

pub struct RegisterToken {
coin_type: TypeTag,
}
Expand All @@ -41,6 +47,7 @@ pub enum OpsDetails {
TokenTransfer(TokenTransfer),
ImplicitTokenTransfer(TokenTransfer),
NftOps(NftOperation),
FungibleAssetTransfer(FungibleAssetTransfer),
}

fn setup_proto_transaction<'a>(
Expand Down Expand Up @@ -135,6 +142,20 @@ fn setup_proto_transaction<'a>(
panic!("Unsupported arguments")
}
},
"fungible_asset_transfer" => {
if let OpsDetails::FungibleAssetTransfer(fungible_asset_transfer) = ops_details.unwrap()
{
Proto::mod_SigningInput::OneOftransaction_payload::fungible_asset_transfer(
Proto::FungibleAssetTransferMessage {
to: fungible_asset_transfer.to.into(),
amount: fungible_asset_transfer.amount,
metadata_address: fungible_asset_transfer.metadata_address.into(),
},
)
} else {
panic!("Unsupported arguments")
}
},
"blind_sign_json" => Proto::mod_SigningInput::OneOftransaction_payload::None,
_ => Proto::mod_SigningInput::OneOftransaction_payload::None,
};
Expand Down Expand Up @@ -321,6 +342,52 @@ fn test_aptos_sign_coin_transfer() {
}"#);
}

// Successfully broadcasted https://explorer.aptoslabs.com/txn/0x475fc97bcba87907166a720676e1b2f5320e613fd13014df37dcf17b09ff0e98/balanceChange?network=mainnet
#[test]
fn test_aptos_sign_fungible_asset_transfer() {
let input = setup_proto_transaction(
"0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address
"5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair
"fungible_asset_transfer",
74, // Sequence number
1,
20,
1736060099,
100,
"",
"",
Some(OpsDetails::FungibleAssetTransfer(FungibleAssetTransfer {
metadata_address: "0x2ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12"
.to_string(),
to: "0x2d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e52".to_string(),
amount: 100000000,
})),
);
let output = Signer::sign_proto(input);
test_tx_result(output,
"07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a670000000001", // Expected raw transaction bytes
"2d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c", // Expected signature
"07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a6700000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c402d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c", // Expected encoded transaction
r#"{
"expiration_timestamp_secs": "1736060099",
"gas_unit_price": "100",
"max_gas_amount": "20",
"payload": {
"arguments": ["0x2ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12","0x2d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e52", "100000000"],
"function": "0x1::primary_fungible_store::transfer",
"type": "entry_function_payload",
"type_arguments": ["0x1::fungible_asset::Metadata"]
},
"sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30",
"sequence_number": "74",
"signature": {
"public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c",
"signature": "0x2d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c",
"type": "ed25519_signature"
}
}"#);
}

// Successfully broadcasted https://explorer.aptoslabs.com/txn/0x197d40ea12e2bfc65a0a913b9f4ca3b0b0208fe0c1514d3d55cef3d5bcf25211?network=mainnet
#[test]
fn test_implicit_aptos_sign_coin_transfer() {
Expand Down
10 changes: 10 additions & 0 deletions src/proto/Aptos.proto
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ message TokenTransferCoinsMessage {
StructTag function = 3;
}

message FungibleAssetTransferMessage {
// Fungible Asset address (string)
string metadata_address = 1;
// Destination Account address (string)
string to = 2;
// Amount to be transferred (uint64)
uint64 amount = 3;
}

// Necessary fields to process a ManagedTokensRegisterMessage
message ManagedTokensRegisterMessage {
// token function to register, e.g BTC: 0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC
Expand Down Expand Up @@ -165,6 +174,7 @@ message SigningInput {
ManagedTokensRegisterMessage register_token = 13;
LiquidStaking liquid_staking_message = 14;
TokenTransferCoinsMessage token_transfer_coins = 15;
FungibleAssetTransferMessage fungible_asset_transfer = 16;
}

string abi = 21;
Expand Down
27 changes: 27 additions & 0 deletions swift/Tests/Blockchains/AptosTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,31 @@ class AptosTests: XCTestCase {
XCTAssertEqual(output.authenticator.signature.hexString, expectedSignature)
XCTAssertEqual(output.encoded.hexString, expectedSignedTx)
}

func testSignFungibleAssetTransfer() {
// Successfully broadcasted https://explorer.aptoslabs.com/txn/0x475fc97bcba87907166a720676e1b2f5320e613fd13014df37dcf17b09ff0e98/balanceChange?network=mainnet
let privateKeyData = Data(hexString: "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")!
let fungibleAssetTransferMsg = AptosFungibleAssetTransferMessage.with {
$0.metadataAddress = "0x2ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12"
$0.to = "0x2d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e52"
$0.amount = 100000000
}
let input = AptosSigningInput.with {
$0.chainID = 1
$0.sender = "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"
$0.expirationTimestampSecs = 1736060099
$0.gasUnitPrice = 100
$0.maxGasAmount = 20
$0.sequenceNumber = 74
$0.fungibleAssetTransfer = fungibleAssetTransferMsg
$0.privateKey = privateKeyData
}
let output: AptosSigningOutput = AnySigner.sign(input: input, coin: .aptos)
let expectedRawTx = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a670000000001"
let expectedSignature = "2d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c"
let expectedSignedTx = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a6700000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c402d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c"
XCTAssertEqual(output.rawTxn.hexString, expectedRawTx)
XCTAssertEqual(output.authenticator.signature.hexString, expectedSignature)
XCTAssertEqual(output.encoded.hexString, expectedSignedTx)
}
}

0 comments on commit 6bae053

Please sign in to comment.