diff --git a/aptos-move/framework/aptos-framework/doc/account.md b/aptos-move/framework/aptos-framework/doc/account.md index b51fc35cbdc71..9cf42dd9990a0 100644 --- a/aptos-move/framework/aptos-framework/doc/account.md +++ b/aptos-move/framework/aptos-framework/doc/account.md @@ -692,7 +692,7 @@ Scheme identifier for MultiEd25519 signatures used to derive authentication keys -
fun create_signer(addr: address): signer
+public(friend) fun create_signer(addr: address): signer
@@ -701,7 +701,7 @@ Scheme identifier for MultiEd25519 signatures used to derive authentication keys
Implementation
-native fun create_signer(addr: address): signer;
+public(friend) native fun create_signer(addr: address): signer;
@@ -1527,7 +1527,7 @@ Capability based functions for efficient use.
### Function `create_signer`
-fun create_signer(addr: address): signer
+public(friend) fun create_signer(addr: address): signer
diff --git a/aptos-move/framework/aptos-framework/doc/aptos_account.md b/aptos-move/framework/aptos-framework/doc/aptos_account.md
index eff1a3d3fb7fa..446b3f062dee5 100644
--- a/aptos-move/framework/aptos-framework/doc/aptos_account.md
+++ b/aptos-move/framework/aptos-framework/doc/aptos_account.md
@@ -5,11 +5,19 @@
+- [Resource `DirectTransferConfig`](#0x1_aptos_account_DirectTransferConfig)
+- [Struct `DirectCoinTransferConfigUpdatedEvent`](#0x1_aptos_account_DirectCoinTransferConfigUpdatedEvent)
- [Constants](#@Constants_0)
- [Function `create_account`](#0x1_aptos_account_create_account)
+- [Function `batch_transfer`](#0x1_aptos_account_batch_transfer)
- [Function `transfer`](#0x1_aptos_account_transfer)
+- [Function `batch_transfer_coins`](#0x1_aptos_account_batch_transfer_coins)
+- [Function `transfer_coins`](#0x1_aptos_account_transfer_coins)
+- [Function `deposit_coins`](#0x1_aptos_account_deposit_coins)
- [Function `assert_account_exists`](#0x1_aptos_account_assert_account_exists)
- [Function `assert_account_is_registered_for_apt`](#0x1_aptos_account_assert_account_is_registered_for_apt)
+- [Function `set_allow_direct_coin_transfers`](#0x1_aptos_account_set_allow_direct_coin_transfers)
+- [Function `can_receive_direct_coin_transfers`](#0x1_aptos_account_can_receive_direct_coin_transfers)
- [Specification](#@Specification_1)
- [Function `create_account`](#@Specification_1_create_account)
- [Function `transfer`](#@Specification_1_transfer)
@@ -21,15 +29,101 @@
use 0x1::aptos_coin;
use 0x1::coin;
use 0x1::error;
+use 0x1::event;
+use 0x1::signer;
+
+
+## Resource `DirectTransferConfig`
+
+Configuration for whether an account can receive direct transfers of coins that they have not registered.
+
+By default, this is enabled. Users can opt-out by disabling at any time.
+
+
+struct DirectTransferConfig has key
+
+
+
+
+
+Fields
+
+
+
+-
+
allow_arbitrary_coin_transfers: bool
+
+-
+
+
+-
+
update_coin_transfer_events: event::EventHandle<aptos_account::DirectCoinTransferConfigUpdatedEvent>
+
+-
+
+
+
+
+
+
+
+
+
+## Struct `DirectCoinTransferConfigUpdatedEvent`
+
+Event emitted when an account's direct coins transfer config is updated.
+
+
+struct DirectCoinTransferConfigUpdatedEvent has drop, store
+
+
+
+
+
+Fields
+
+
+
+-
+
new_allow_direct_transfers: bool
+
+-
+
+
+
+
+
+
+
## Constants
+
+
+Account opted out of receiving coins that they did not register to receive.
+
+
+const EACCOUNT_DOES_NOT_ACCEPT_DIRECT_COIN_TRANSFERS: u64 = 3;
+
+
+
+
+
+
+Account opted out of directly receiving NFT tokens.
+
+
+const EACCOUNT_DOES_NOT_ACCEPT_DIRECT_TOKEN_TRANSFERS: u64 = 4;
+
+
+
+
Account does not exist.
@@ -50,6 +144,16 @@ Account is not registered to receive APT.
+
+
+The lengths of the recipients and amounts lists don't match.
+
+
+const EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH: u64 = 5;
+
+
+
+
## Function `create_account`
@@ -74,12 +178,51 @@ Basic account creation methods.
+
+
+
+
+## Function `batch_transfer`
+
+Batch version of APT transfer.
+
+
+public entry fun batch_transfer(source: &signer, recipients: vector<address>, amounts: vector<u64>)
+
+
+
+
+
+Implementation
+
+
+public entry fun batch_transfer(source: &signer, recipients: vector<address>, amounts: vector<u64>) {
+ let recipients_len = vector::length(&recipients);
+ assert!(
+ recipients_len == vector::length(&amounts),
+ error::invalid_argument(EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH),
+ );
+
+ let i = 0;
+ while (i < recipients_len) {
+ let to = *vector::borrow(&recipients, i);
+ let amount = *vector::borrow(&amounts, i);
+ transfer(source, to, amount);
+ i = i + 1;
+ };
+}
+
+
+
+
## Function `transfer`
+Convenient function to transfer APT to a recipient account that might not exist.
+This would create the recipient account first, which also registers it to receive APT, before transferring.
public entry fun transfer(source: &signer, to: address, amount: u64)
@@ -101,6 +244,106 @@ Basic account creation methods.
+
+
+
+
+## Function `batch_transfer_coins`
+
+Batch version of transfer_coins.
+
+
+public entry fun batch_transfer_coins<CoinType>(from: &signer, recipients: vector<address>, amounts: vector<u64>)
+
+
+
+
+
+Implementation
+
+
+public entry fun batch_transfer_coins<CoinType>(
+ from: &signer, recipients: vector<address>, amounts: vector<u64>) acquires DirectTransferConfig {
+ let recipients_len = vector::length(&recipients);
+ assert!(
+ recipients_len == vector::length(&amounts),
+ error::invalid_argument(EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH),
+ );
+
+ let i = 0;
+ while (i < recipients_len) {
+ let to = *vector::borrow(&recipients, i);
+ let amount = *vector::borrow(&amounts, i);
+ transfer_coins<CoinType>(from, to, amount);
+ i = i + 1;
+ };
+}
+
+
+
+
+
+
+
+
+## Function `transfer_coins`
+
+Convenient function to transfer a custom CoinType to a recipient account that might not exist.
+This would create the recipient account first and register it to receive the CoinType, before transferring.
+
+
+public entry fun transfer_coins<CoinType>(from: &signer, to: address, amount: u64)
+
+
+
+
+
+Implementation
+
+
+public entry fun transfer_coins<CoinType>(from: &signer, to: address, amount: u64) acquires DirectTransferConfig {
+ deposit_coins(to, coin::withdraw<CoinType>(from, amount));
+}
+
+
+
+
+
+
+
+
+## Function `deposit_coins`
+
+Convenient function to deposit a custom CoinType into a recipient account that might not exist.
+This would create the recipient account first and register it to receive the CoinType, before transferring.
+
+
+public fun deposit_coins<CoinType>(to: address, coins: coin::Coin<CoinType>)
+
+
+
+
+
+Implementation
+
+
+public fun deposit_coins<CoinType>(to: address, coins: Coin<CoinType>) acquires DirectTransferConfig {
+ if (!account::exists_at(to)) {
+ create_account(to);
+ };
+ if (!coin::is_account_registered<CoinType>(to)) {
+ assert!(
+ can_receive_direct_coin_transfers(to),
+ error::permission_denied(EACCOUNT_DOES_NOT_ACCEPT_DIRECT_COIN_TRANSFERS),
+ );
+ coin::register<CoinType>(&account::create_signer(to));
+ };
+ coin::deposit<CoinType>(to, coins)
+}
+
+
+
+
@@ -150,6 +393,81 @@ Basic account creation methods.
+
+
+
+
+## Function `set_allow_direct_coin_transfers`
+
+Set whether account
can receive direct transfers of coins that they have not explicitly registered to receive.
+
+
+public entry fun set_allow_direct_coin_transfers(account: &signer, allow: bool)
+
+
+
+
+
+Implementation
+
+
+public entry fun set_allow_direct_coin_transfers(account: &signer, allow: bool) acquires DirectTransferConfig {
+ let addr = signer::address_of(account);
+ if (exists<DirectTransferConfig>(addr)) {
+ let direct_transfer_config = borrow_global_mut<DirectTransferConfig>(addr);
+ // Short-circuit to avoid emitting an event if direct transfer config is not changing.
+ if (direct_transfer_config.allow_arbitrary_coin_transfers == allow) {
+ return
+ };
+
+ direct_transfer_config.allow_arbitrary_coin_transfers = allow;
+ emit_event(
+ &mut direct_transfer_config.update_coin_transfer_events,
+ DirectCoinTransferConfigUpdatedEvent { new_allow_direct_transfers: allow });
+ } else {
+ let direct_transfer_config = DirectTransferConfig {
+ allow_arbitrary_coin_transfers: allow,
+ update_coin_transfer_events: new_event_handle<DirectCoinTransferConfigUpdatedEvent>(account),
+ };
+ emit_event(
+ &mut direct_transfer_config.update_coin_transfer_events,
+ DirectCoinTransferConfigUpdatedEvent { new_allow_direct_transfers: allow });
+ move_to(account, direct_transfer_config);
+ };
+}
+
+
+
+
+
+
+
+
+## Function `can_receive_direct_coin_transfers`
+
+Return true if account
can receive direct transfers of coins that they have not explicitly registered to
+receive.
+
+By default, this returns true if an account has not explicitly set whether the can receive direct transfers.
+
+
+public fun can_receive_direct_coin_transfers(account: address): bool
+
+
+
+
+
+Implementation
+
+
+public fun can_receive_direct_coin_transfers(account: address): bool acquires DirectTransferConfig {
+ !exists<DirectTransferConfig>(account) ||
+ borrow_global<DirectTransferConfig>(account).allow_arbitrary_coin_transfers
+}
+
+
+
+
diff --git a/aptos-move/framework/aptos-framework/sources/account.move b/aptos-move/framework/aptos-framework/sources/account.move
index 89631984f031e..91e735bcbabc2 100644
--- a/aptos-move/framework/aptos-framework/sources/account.move
+++ b/aptos-move/framework/aptos-framework/sources/account.move
@@ -127,7 +127,7 @@ module aptos_framework::account {
/// An attempt to create a resource account on an account that has a committed transaction
const EACCOUNT_ALREADY_USED: u64 = 16;
- native fun create_signer(addr: address): signer;
+ public(friend) native fun create_signer(addr: address): signer;
#[test_only]
/// Create signer for testing, independently of an Aptos-style `Account`.
diff --git a/aptos-move/framework/aptos-framework/sources/aptos_account.move b/aptos-move/framework/aptos-framework/sources/aptos_account.move
index fef7272bf9cf0..ff6bc25a3e53f 100644
--- a/aptos-move/framework/aptos-framework/sources/aptos_account.move
+++ b/aptos-move/framework/aptos-framework/sources/aptos_account.move
@@ -1,9 +1,11 @@
module aptos_framework::aptos_account {
- use std::error;
-
- use aptos_framework::account;
+ use aptos_framework::account::{Self, new_event_handle};
use aptos_framework::aptos_coin::AptosCoin;
- use aptos_framework::coin;
+ use aptos_framework::coin::{Self, Coin};
+ use aptos_framework::event::{EventHandle, emit_event};
+ use std::error;
+ use std::signer;
+ use std::vector;
friend aptos_framework::genesis;
friend aptos_framework::resource_account;
@@ -12,6 +14,25 @@ module aptos_framework::aptos_account {
const EACCOUNT_NOT_FOUND: u64 = 1;
/// Account is not registered to receive APT.
const EACCOUNT_NOT_REGISTERED_FOR_APT: u64 = 2;
+ /// Account opted out of receiving coins that they did not register to receive.
+ const EACCOUNT_DOES_NOT_ACCEPT_DIRECT_COIN_TRANSFERS: u64 = 3;
+ /// Account opted out of directly receiving NFT tokens.
+ const EACCOUNT_DOES_NOT_ACCEPT_DIRECT_TOKEN_TRANSFERS: u64 = 4;
+ /// The lengths of the recipients and amounts lists don't match.
+ const EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH: u64 = 5;
+
+ /// Configuration for whether an account can receive direct transfers of coins that they have not registered.
+ ///
+ /// By default, this is enabled. Users can opt-out by disabling at any time.
+ struct DirectTransferConfig has key {
+ allow_arbitrary_coin_transfers: bool,
+ update_coin_transfer_events: EventHandle,
+ }
+
+ /// Event emitted when an account's direct coins transfer config is updated.
+ struct DirectCoinTransferConfigUpdatedEvent has drop, store {
+ new_allow_direct_transfers: bool,
+ }
///////////////////////////////////////////////////////////////////////////
/// Basic account creation methods.
@@ -22,6 +43,25 @@ module aptos_framework::aptos_account {
coin::register(&signer);
}
+ /// Batch version of APT transfer.
+ public entry fun batch_transfer(source: &signer, recipients: vector, amounts: vector) {
+ let recipients_len = vector::length(&recipients);
+ assert!(
+ recipients_len == vector::length(&amounts),
+ error::invalid_argument(EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH),
+ );
+
+ let i = 0;
+ while (i < recipients_len) {
+ let to = *vector::borrow(&recipients, i);
+ let amount = *vector::borrow(&amounts, i);
+ transfer(source, to, amount);
+ i = i + 1;
+ };
+ }
+
+ /// Convenient function to transfer APT to a recipient account that might not exist.
+ /// This would create the recipient account first, which also registers it to receive APT, before transferring.
public entry fun transfer(source: &signer, to: address, amount: u64) {
if (!account::exists_at(to)) {
create_account(to)
@@ -29,6 +69,46 @@ module aptos_framework::aptos_account {
coin::transfer(source, to, amount)
}
+ /// Batch version of transfer_coins.
+ public entry fun batch_transfer_coins(
+ from: &signer, recipients: vector, amounts: vector) acquires DirectTransferConfig {
+ let recipients_len = vector::length(&recipients);
+ assert!(
+ recipients_len == vector::length(&amounts),
+ error::invalid_argument(EMISMATCHING_RECIPIENTS_AND_AMOUNTS_LENGTH),
+ );
+
+ let i = 0;
+ while (i < recipients_len) {
+ let to = *vector::borrow(&recipients, i);
+ let amount = *vector::borrow(&amounts, i);
+ transfer_coins(from, to, amount);
+ i = i + 1;
+ };
+ }
+
+ /// Convenient function to transfer a custom CoinType to a recipient account that might not exist.
+ /// This would create the recipient account first and register it to receive the CoinType, before transferring.
+ public entry fun transfer_coins(from: &signer, to: address, amount: u64) acquires DirectTransferConfig {
+ deposit_coins(to, coin::withdraw(from, amount));
+ }
+
+ /// Convenient function to deposit a custom CoinType into a recipient account that might not exist.
+ /// This would create the recipient account first and register it to receive the CoinType, before transferring.
+ public fun deposit_coins(to: address, coins: Coin) acquires DirectTransferConfig {
+ if (!account::exists_at(to)) {
+ create_account(to);
+ };
+ if (!coin::is_account_registered(to)) {
+ assert!(
+ can_receive_direct_coin_transfers(to),
+ error::permission_denied(EACCOUNT_DOES_NOT_ACCEPT_DIRECT_COIN_TRANSFERS),
+ );
+ coin::register(&account::create_signer(to));
+ };
+ coin::deposit(to, coins)
+ }
+
public fun assert_account_exists(addr: address) {
assert!(account::exists_at(addr), error::not_found(EACCOUNT_NOT_FOUND));
}
@@ -38,26 +118,197 @@ module aptos_framework::aptos_account {
assert!(coin::is_account_registered(addr), error::not_found(EACCOUNT_NOT_REGISTERED_FOR_APT));
}
- #[test(alice = @0xa11ce, core = @0x1)]
- public fun test_transfer(alice: signer, core: signer) {
- use std::signer;
- use aptos_std::from_bcs;
+ /// Set whether `account` can receive direct transfers of coins that they have not explicitly registered to receive.
+ public entry fun set_allow_direct_coin_transfers(account: &signer, allow: bool) acquires DirectTransferConfig {
+ let addr = signer::address_of(account);
+ if (exists(addr)) {
+ let direct_transfer_config = borrow_global_mut(addr);
+ // Short-circuit to avoid emitting an event if direct transfer config is not changing.
+ if (direct_transfer_config.allow_arbitrary_coin_transfers == allow) {
+ return
+ };
+
+ direct_transfer_config.allow_arbitrary_coin_transfers = allow;
+ emit_event(
+ &mut direct_transfer_config.update_coin_transfer_events,
+ DirectCoinTransferConfigUpdatedEvent { new_allow_direct_transfers: allow });
+ } else {
+ let direct_transfer_config = DirectTransferConfig {
+ allow_arbitrary_coin_transfers: allow,
+ update_coin_transfer_events: new_event_handle(account),
+ };
+ emit_event(
+ &mut direct_transfer_config.update_coin_transfer_events,
+ DirectCoinTransferConfigUpdatedEvent { new_allow_direct_transfers: allow });
+ move_to(account, direct_transfer_config);
+ };
+ }
+
+ /// Return true if `account` can receive direct transfers of coins that they have not explicitly registered to
+ /// receive.
+ ///
+ /// By default, this returns true if an account has not explicitly set whether the can receive direct transfers.
+ public fun can_receive_direct_coin_transfers(account: address): bool acquires DirectTransferConfig {
+ !exists(account) ||
+ borrow_global(account).allow_arbitrary_coin_transfers
+ }
+
+ #[test_only]
+ use aptos_std::from_bcs;
+ #[test_only]
+ use std::string::utf8;
+ #[test_only]
+ use aptos_framework::account::create_account_for_test;
+ #[test_only]
+ struct FakeCoin {}
+
+ #[test(alice = @0xa11ce, core = @0x1)]
+ public fun test_transfer(alice: &signer, core: &signer) {
let bob = from_bcs::to_address(x"0000000000000000000000000000000000000000000000000000000000000b0b");
let carol = from_bcs::to_address(x"00000000000000000000000000000000000000000000000000000000000ca501");
- let (burn_cap, mint_cap) = aptos_framework::aptos_coin::initialize_for_test(&core);
- create_account(signer::address_of(&alice));
- coin::deposit(signer::address_of(&alice), coin::mint(10000, &mint_cap));
- transfer(&alice, bob, 500);
+ let (burn_cap, mint_cap) = aptos_framework::aptos_coin::initialize_for_test(core);
+ create_account(signer::address_of(alice));
+ coin::deposit(signer::address_of(alice), coin::mint(10000, &mint_cap));
+ transfer(alice, bob, 500);
assert!(coin::balance(bob) == 500, 0);
- transfer(&alice, carol, 500);
+ transfer(alice, carol, 500);
assert!(coin::balance(carol) == 500, 1);
- transfer(&alice, carol, 1500);
+ transfer(alice, carol, 1500);
assert!(coin::balance(carol) == 2000, 2);
coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
- let _bob = bob;
+ }
+
+ #[test(from = @0x123, core = @0x1, recipient_1 = @0x124, recipient_2 = @0x125)]
+ public fun test_batch_transfer(from: &signer, core: &signer, recipient_1: &signer, recipient_2: &signer) {
+ let (burn_cap, mint_cap) = aptos_framework::aptos_coin::initialize_for_test(core);
+ create_account(signer::address_of(from));
+ let recipient_1_addr = signer::address_of(recipient_1);
+ let recipient_2_addr = signer::address_of(recipient_2);
+ create_account(recipient_1_addr);
+ create_account(recipient_2_addr);
+ coin::deposit(signer::address_of(from), coin::mint(10000, &mint_cap));
+ batch_transfer(
+ from,
+ vector[recipient_1_addr, recipient_2_addr],
+ vector[100, 500],
+ );
+ assert!(coin::balance(recipient_1_addr) == 100, 0);
+ assert!(coin::balance(recipient_2_addr) == 500, 1);
+ coin::destroy_burn_cap(burn_cap);
+ coin::destroy_mint_cap(mint_cap);
+ }
+
+ #[test(from = @0x1, to = @0x12)]
+ public fun test_direct_coin_transfers(from: &signer, to: &signer) acquires DirectTransferConfig {
+ let (burn_cap, freeze_cap, mint_cap) = coin::initialize(
+ from,
+ utf8(b"FC"),
+ utf8(b"FC"),
+ 10,
+ true,
+ );
+ create_account_for_test(signer::address_of(from));
+ create_account_for_test(signer::address_of(to));
+ deposit_coins(signer::address_of(from), coin::mint(1000, &mint_cap));
+ // Recipient account did not explicit register for the coin.
+ let to_addr = signer::address_of(to);
+ transfer_coins(from, to_addr, 500);
+ assert!(coin::balance(to_addr) == 500, 0);
+
+ coin::destroy_burn_cap(burn_cap);
+ coin::destroy_mint_cap(mint_cap);
+ coin::destroy_freeze_cap(freeze_cap);
+ }
+
+ #[test(from = @0x1, recipient_1 = @0x124, recipient_2 = @0x125)]
+ public fun test_batch_transfer_coins(
+ from: &signer, recipient_1: &signer, recipient_2: &signer) acquires DirectTransferConfig {
+ let (burn_cap, freeze_cap, mint_cap) = coin::initialize(
+ from,
+ utf8(b"FC"),
+ utf8(b"FC"),
+ 10,
+ true,
+ );
+ create_account_for_test(signer::address_of(from));
+ let recipient_1_addr = signer::address_of(recipient_1);
+ let recipient_2_addr = signer::address_of(recipient_2);
+ create_account_for_test(recipient_1_addr);
+ create_account_for_test(recipient_2_addr);
+ deposit_coins(signer::address_of(from), coin::mint(1000, &mint_cap));
+ batch_transfer_coins(
+ from,
+ vector[recipient_1_addr, recipient_2_addr],
+ vector[100, 500],
+ );
+ assert!(coin::balance(recipient_1_addr) == 100, 0);
+ assert!(coin::balance(recipient_2_addr) == 500, 1);
+
+ coin::destroy_burn_cap(burn_cap);
+ coin::destroy_mint_cap(mint_cap);
+ coin::destroy_freeze_cap(freeze_cap);
+ }
+
+ #[test(user = @0x123)]
+ public fun test_set_allow_direct_coin_transfers(user: &signer) acquires DirectTransferConfig {
+ let addr = signer::address_of(user);
+ create_account_for_test(addr);
+ set_allow_direct_coin_transfers(user, true);
+ assert!(can_receive_direct_coin_transfers(addr), 0);
+ set_allow_direct_coin_transfers(user, false);
+ assert!(!can_receive_direct_coin_transfers(addr), 1);
+ set_allow_direct_coin_transfers(user, true);
+ assert!(can_receive_direct_coin_transfers(addr), 2);
+ }
+
+ #[test(from = @0x1, to = @0x12)]
+ public fun test_direct_coin_transfers_with_explicit_direct_coin_transfer_config(
+ from: &signer, to: &signer) acquires DirectTransferConfig {
+ let (burn_cap, freeze_cap, mint_cap) = coin::initialize(
+ from,
+ utf8(b"FC"),
+ utf8(b"FC"),
+ 10,
+ true,
+ );
+ create_account_for_test(signer::address_of(from));
+ create_account_for_test(signer::address_of(to));
+ set_allow_direct_coin_transfers(from, true);
+ deposit_coins(signer::address_of(from), coin::mint(1000, &mint_cap));
+ // Recipient account did not explicit register for the coin.
+ let to_addr = signer::address_of(to);
+ transfer_coins(from, to_addr, 500);
+ assert!(coin::balance(to_addr) == 500, 0);
+
+ coin::destroy_burn_cap(burn_cap);
+ coin::destroy_mint_cap(mint_cap);
+ coin::destroy_freeze_cap(freeze_cap);
+ }
+
+ #[test(from = @0x1, to = @0x12)]
+ #[expected_failure(abort_code = 0x50003, location = Self)]
+ public fun test_direct_coin_transfers_fail_if_recipient_opted_out(
+ from: &signer, to: &signer) acquires DirectTransferConfig {
+ let (burn_cap, freeze_cap, mint_cap) = coin::initialize(
+ from,
+ utf8(b"FC"),
+ utf8(b"FC"),
+ 10,
+ true,
+ );
+ create_account_for_test(signer::address_of(from));
+ create_account_for_test(signer::address_of(to));
+ set_allow_direct_coin_transfers(from, false);
+ deposit_coins(signer::address_of(from), coin::mint(1000, &mint_cap));
+ // This should fail as the to account has explicitly opted out of receiving arbitrary coins.
+ transfer_coins(from, signer::address_of(to), 500);
+
+ coin::destroy_burn_cap(burn_cap);
+ coin::destroy_mint_cap(mint_cap);
+ coin::destroy_freeze_cap(freeze_cap);
}
}
diff --git a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs
index bda94e758d985..c1445729588bd 100644
--- a/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs
+++ b/aptos-move/framework/cached-packages/src/aptos_framework_sdk_builder.rs
@@ -76,16 +76,44 @@ pub enum EntryFunctionCall {
cap_update_table: Vec,
},
+ /// Batch version of APT transfer.
+ AptosAccountBatchTransfer {
+ recipients: Vec,
+ amounts: Vec,
+ },
+
+ /// Batch version of transfer_coins.
+ AptosAccountBatchTransferCoins {
+ coin_type: TypeTag,
+ recipients: Vec,
+ amounts: Vec,
+ },
+
/// Basic account creation methods.
AptosAccountCreateAccount {
auth_key: AccountAddress,
},
+ /// Set whether `account` can receive direct transfers of coins that they have not explicitly registered to receive.
+ AptosAccountSetAllowDirectCoinTransfers {
+ allow: bool,
+ },
+
+ /// Convenient function to transfer APT to a recipient account that might not exist.
+ /// This would create the recipient account first, which also registers it to receive APT, before transferring.
AptosAccountTransfer {
to: AccountAddress,
amount: u64,
},
+ /// Convenient function to transfer a custom CoinType to a recipient account that might not exist.
+ /// This would create the recipient account first and register it to receive the CoinType, before transferring.
+ AptosAccountTransferCoins {
+ coin_type: TypeTag,
+ to: AccountAddress,
+ amount: u64,
+ },
+
/// Only callable in tests and testnets where the core resources account exists.
/// Claim the delegated mint capability and destroy the delegated token.
AptosCoinClaimMintCapability {},
@@ -507,8 +535,25 @@ impl EntryFunctionCall {
cap_rotate_key,
cap_update_table,
),
+ AptosAccountBatchTransfer {
+ recipients,
+ amounts,
+ } => aptos_account_batch_transfer(recipients, amounts),
+ AptosAccountBatchTransferCoins {
+ coin_type,
+ recipients,
+ amounts,
+ } => aptos_account_batch_transfer_coins(coin_type, recipients, amounts),
AptosAccountCreateAccount { auth_key } => aptos_account_create_account(auth_key),
+ AptosAccountSetAllowDirectCoinTransfers { allow } => {
+ aptos_account_set_allow_direct_coin_transfers(allow)
+ }
AptosAccountTransfer { to, amount } => aptos_account_transfer(to, amount),
+ AptosAccountTransferCoins {
+ coin_type,
+ to,
+ amount,
+ } => aptos_account_transfer_coins(coin_type, to, amount),
AptosCoinClaimMintCapability {} => aptos_coin_claim_mint_capability(),
AptosCoinDelegateMintCapability { to } => aptos_coin_delegate_mint_capability(to),
AptosCoinMint { dst_addr, amount } => aptos_coin_mint(dst_addr, amount),
@@ -858,6 +903,51 @@ pub fn account_rotate_authentication_key(
))
}
+/// Batch version of APT transfer.
+pub fn aptos_account_batch_transfer(
+ recipients: Vec,
+ amounts: Vec,
+) -> TransactionPayload {
+ 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!("aptos_account").to_owned(),
+ ),
+ ident_str!("batch_transfer").to_owned(),
+ vec![],
+ vec![
+ bcs::to_bytes(&recipients).unwrap(),
+ bcs::to_bytes(&amounts).unwrap(),
+ ],
+ ))
+}
+
+/// Batch version of transfer_coins.
+pub fn aptos_account_batch_transfer_coins(
+ coin_type: TypeTag,
+ recipients: Vec,
+ amounts: Vec,
+) -> TransactionPayload {
+ 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!("aptos_account").to_owned(),
+ ),
+ ident_str!("batch_transfer_coins").to_owned(),
+ vec![coin_type],
+ vec![
+ bcs::to_bytes(&recipients).unwrap(),
+ bcs::to_bytes(&amounts).unwrap(),
+ ],
+ ))
+}
+
/// Basic account creation methods.
pub fn aptos_account_create_account(auth_key: AccountAddress) -> TransactionPayload {
TransactionPayload::EntryFunction(EntryFunction::new(
@@ -874,6 +964,24 @@ pub fn aptos_account_create_account(auth_key: AccountAddress) -> TransactionPayl
))
}
+/// Set whether `account` can receive direct transfers of coins that they have not explicitly registered to receive.
+pub fn aptos_account_set_allow_direct_coin_transfers(allow: bool) -> TransactionPayload {
+ 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!("aptos_account").to_owned(),
+ ),
+ ident_str!("set_allow_direct_coin_transfers").to_owned(),
+ vec![],
+ vec![bcs::to_bytes(&allow).unwrap()],
+ ))
+}
+
+/// Convenient function to transfer APT to a recipient account that might not exist.
+/// This would create the recipient account first, which also registers it to receive APT, before transferring.
pub fn aptos_account_transfer(to: AccountAddress, amount: u64) -> TransactionPayload {
TransactionPayload::EntryFunction(EntryFunction::new(
ModuleId::new(
@@ -889,6 +997,27 @@ pub fn aptos_account_transfer(to: AccountAddress, amount: u64) -> TransactionPay
))
}
+/// Convenient function to transfer a custom CoinType to a recipient account that might not exist.
+/// This would create the recipient account first and register it to receive the CoinType, before transferring.
+pub fn aptos_account_transfer_coins(
+ coin_type: TypeTag,
+ to: AccountAddress,
+ amount: u64,
+) -> TransactionPayload {
+ 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!("aptos_account").to_owned(),
+ ),
+ ident_str!("transfer_coins").to_owned(),
+ vec![coin_type],
+ vec![bcs::to_bytes(&to).unwrap(), bcs::to_bytes(&amount).unwrap()],
+ ))
+}
+
/// Only callable in tests and testnets where the core resources account exists.
/// Claim the delegated mint capability and destroy the delegated token.
pub fn aptos_coin_claim_mint_capability() -> TransactionPayload {
@@ -2191,6 +2320,31 @@ mod decoder {
}
}
+ pub fn aptos_account_batch_transfer(payload: &TransactionPayload) -> Option {
+ if let TransactionPayload::EntryFunction(script) = payload {
+ Some(EntryFunctionCall::AptosAccountBatchTransfer {
+ recipients: bcs::from_bytes(script.args().get(0)?).ok()?,
+ amounts: bcs::from_bytes(script.args().get(1)?).ok()?,
+ })
+ } else {
+ None
+ }
+ }
+
+ pub fn aptos_account_batch_transfer_coins(
+ payload: &TransactionPayload,
+ ) -> Option {
+ if let TransactionPayload::EntryFunction(script) = payload {
+ Some(EntryFunctionCall::AptosAccountBatchTransferCoins {
+ coin_type: script.ty_args().get(0)?.clone(),
+ recipients: bcs::from_bytes(script.args().get(0)?).ok()?,
+ amounts: bcs::from_bytes(script.args().get(1)?).ok()?,
+ })
+ } else {
+ None
+ }
+ }
+
pub fn aptos_account_create_account(payload: &TransactionPayload) -> Option {
if let TransactionPayload::EntryFunction(script) = payload {
Some(EntryFunctionCall::AptosAccountCreateAccount {
@@ -2201,6 +2355,18 @@ mod decoder {
}
}
+ pub fn aptos_account_set_allow_direct_coin_transfers(
+ payload: &TransactionPayload,
+ ) -> Option {
+ if let TransactionPayload::EntryFunction(script) = payload {
+ Some(EntryFunctionCall::AptosAccountSetAllowDirectCoinTransfers {
+ allow: bcs::from_bytes(script.args().get(0)?).ok()?,
+ })
+ } else {
+ None
+ }
+ }
+
pub fn aptos_account_transfer(payload: &TransactionPayload) -> Option {
if let TransactionPayload::EntryFunction(script) = payload {
Some(EntryFunctionCall::AptosAccountTransfer {
@@ -2212,6 +2378,18 @@ mod decoder {
}
}
+ pub fn aptos_account_transfer_coins(payload: &TransactionPayload) -> Option {
+ if let TransactionPayload::EntryFunction(script) = payload {
+ Some(EntryFunctionCall::AptosAccountTransferCoins {
+ coin_type: script.ty_args().get(0)?.clone(),
+ to: bcs::from_bytes(script.args().get(0)?).ok()?,
+ amount: bcs::from_bytes(script.args().get(1)?).ok()?,
+ })
+ } else {
+ None
+ }
+ }
+
pub fn aptos_coin_claim_mint_capability(
payload: &TransactionPayload,
) -> Option {
@@ -2975,14 +3153,30 @@ static SCRIPT_FUNCTION_DECODER_MAP: once_cell::sync::Lazy