diff --git a/aptos-move/framework/aptos-token/doc/token.md b/aptos-move/framework/aptos-token/doc/token.md
index e8b26b598003e..b8ddd7679385b 100644
--- a/aptos-move/framework/aptos-token/doc/token.md
+++ b/aptos-move/framework/aptos-token/doc/token.md
@@ -55,6 +55,7 @@ Checkout our developer doc on our token standard https://aptos.dev/concepts/coin
- [Function `transfer`](#0x3_token_transfer)
- [Function `create_withdraw_capability`](#0x3_token_create_withdraw_capability)
- [Function `withdraw_with_capability`](#0x3_token_withdraw_with_capability)
+- [Function `partial_withdraw_with_capability`](#0x3_token_partial_withdraw_with_capability)
- [Function `withdraw_token`](#0x3_token_withdraw_token)
- [Function `create_collection`](#0x3_token_create_collection)
- [Function `check_collection_exists`](#0x3_token_check_collection_exists)
@@ -1135,6 +1136,16 @@ The field is not mutable
+
+
+Withdraw capability doesn't have sufficient amount
+
+
+
const EINSUFFICIENT_WITHDRAW_CAPABILITY_AMOUNT: u64 = 37;
+
+
+
+
Collection or tokendata maximum must be larger than supply
@@ -2657,6 +2668,60 @@ Withdraw the token with a capability
+
+
+
+
+## Function `partial_withdraw_with_capability`
+
+Withdraw the token with a capability.
+
+
+public fun partial_withdraw_with_capability(withdraw_proof: token::WithdrawCapability, withdraw_amount: u64): (token::Token, option::Option<token::WithdrawCapability>)
+
+
+
+
+
+Implementation
+
+
+public fun partial_withdraw_with_capability(
+ withdraw_proof: WithdrawCapability,
+ withdraw_amount: u64,
+): (Token, Option<WithdrawCapability>) acquires TokenStore {
+ // verify the delegation hasn't expired yet
+ assert!(timestamp::now_seconds() <= *&withdraw_proof.expiration_sec, error::invalid_argument(EWITHDRAW_PROOF_EXPIRES));
+
+ assert!(withdraw_amount <= withdraw_proof.amount, error::invalid_argument(EINSUFFICIENT_WITHDRAW_CAPABILITY_AMOUNT));
+
+ let res: Option<WithdrawCapability> = if (withdraw_amount == withdraw_proof.amount) {
+ option::none<WithdrawCapability>()
+ } else {
+ option::some(
+ WithdrawCapability {
+ token_owner: withdraw_proof.token_owner,
+ token_id: withdraw_proof.token_id,
+ amount: withdraw_proof.amount - withdraw_amount,
+ expiration_sec: withdraw_proof.expiration_sec,
+ }
+ )
+ };
+
+ (
+ withdraw_with_event_internal(
+ withdraw_proof.token_owner,
+ withdraw_proof.token_id,
+ withdraw_amount,
+ ),
+ res
+ )
+
+}
+
+
+
+
diff --git a/aptos-move/framework/aptos-token/sources/token.move b/aptos-move/framework/aptos-token/sources/token.move
index 66af20d225b59..23b23fde88905 100644
--- a/aptos-move/framework/aptos-token/sources/token.move
+++ b/aptos-move/framework/aptos-token/sources/token.move
@@ -145,6 +145,10 @@ module aptos_token::token {
/// Token Properties count doesn't match
const ETOKEN_PROPERTIES_COUNT_NOT_MATCH: u64 = 37;
+
+ /// Withdraw capability doesn't have sufficient amount
+ const EINSUFFICIENT_WITHDRAW_CAPABILITY_AMOUNT: u64 = 38;
+
//
// Core data structures for holding tokens
//
@@ -963,6 +967,40 @@ module aptos_token::token {
)
}
+ /// Withdraw the token with a capability.
+ public fun partial_withdraw_with_capability(
+ withdraw_proof: WithdrawCapability,
+ withdraw_amount: u64,
+ ): (Token, Option) acquires TokenStore {
+ // verify the delegation hasn't expired yet
+ assert!(timestamp::now_seconds() <= *&withdraw_proof.expiration_sec, error::invalid_argument(EWITHDRAW_PROOF_EXPIRES));
+
+ assert!(withdraw_amount <= withdraw_proof.amount, error::invalid_argument(EINSUFFICIENT_WITHDRAW_CAPABILITY_AMOUNT));
+
+ let res: Option = if (withdraw_amount == withdraw_proof.amount) {
+ option::none()
+ } else {
+ option::some(
+ WithdrawCapability {
+ token_owner: withdraw_proof.token_owner,
+ token_id: withdraw_proof.token_id,
+ amount: withdraw_proof.amount - withdraw_amount,
+ expiration_sec: withdraw_proof.expiration_sec,
+ }
+ )
+ };
+
+ (
+ withdraw_with_event_internal(
+ withdraw_proof.token_owner,
+ withdraw_proof.token_id,
+ withdraw_amount,
+ ),
+ res
+ )
+
+ }
+
public fun withdraw_token(
account: &signer,
id: TokenId,
@@ -2448,6 +2486,40 @@ module aptos_token::token {
create_royalty(101, 100, @0xcafe);
}
+ #[test(framework = @0x1, creator = @0xcafe)]
+ fun test_partial_withdraw_with_proof(creator: &signer, framework: &signer): Token acquires TokenStore, Collections {
+ timestamp::set_time_has_started_for_testing(framework);
+ account::create_account_for_test(signer::address_of(creator));
+ let token_id = create_collection_and_token(
+ creator,
+ 4,
+ 4,
+ 4,
+ vector[],
+ vector>[],
+ vector[],
+ vector[false, false, false],
+ vector[false, false, false, false, false],
+ );
+
+ timestamp::update_global_time_for_test(1000000);
+
+ // provide the proof to the account
+ let cap = create_withdraw_capability(
+ creator, // ask user to provide address to avoid ambiguity from rotated keys
+ token_id,
+ 3,
+ 2000000,
+ );
+
+ let (token, capability) = partial_withdraw_with_capability(cap, 1);
+ assert!(option::borrow(&capability).amount == 2, 1);
+ let (token_1, cap) = partial_withdraw_with_capability(option::extract(&mut capability), 2);
+ assert!(option::is_none(&cap), 1);
+ merge(&mut token, token_1);
+ token
+ }
+
//
// Deprecated functions
//