The withdraw pattern

When you want the users of your Blueprint to receive funds, you should not directly send the tokens in their wallet. Instead, you should keep track of how much you owe them internally (using the badge pattern) and write a method allowing them to withdraw the funds. This is a pattern that is also recommended in many other smart contract platforms because it improves composability, security and makes the user pay for their own withdrawal fees.

Example

Imagine you are building a lottery blueprint. You want participants to send XRD to a vault which, later, the winner will receive their prize from. You might think of including a method distribute_funds which, when called by an admin, moves the prize directly to the winner’s wallet. There are multiple problems with this approach. First, this is not really composable. What if the user wanted to do something with the prize tokens in the same transaction ? Second, what if the user provided an address that is not a wallet ? The funds would be locked in the component.

Instead, you should create a withdraw method where users present their badge and if they are the winner, the funds gets returned:

use scrypto::prelude::*;

#[derive(NonFungibleData)]
struct TicketData {
    minted_on: u64
}

blueprint! {
    struct LotteryExample {
        xrd_vault: Vault,

        ticket_minter: Vault,
        ticket_resource_address: ResourceAddress,

        // Here we keep track of the user's NFT badge id
        winner_id: NonFungibleId
    }

    impl LotteryExample {

        pub fn enter_lottery(&mut self, xrd: Bucket) -> Bucket {
            self.xrd_vault.put(xrd);

            // Create a badge to identify this user
            let ticket_badge = self.ticket_minter.authorize(|| {
                let resource_manager: &ResourceManager = borrow_resource_manager!(self.ticket_resource_address);
                resource_manager.mint_non_fungible(
                    &NonFungibleId::random(),
                    TicketData{ minted_on: Runtime::current_epoch() },
                )
            });

            ticket_badge
        }

        // After the winner was picked, they
        // can withdraw the funds
        pub fn withdraw(&mut self, ticket_badge: Proof) -> Bucket {
            assert!(ticket_badge.amount() > Decimal::zero(), "Missing badge");
            assert!(self.winner_id == ticket_badge.non_fungible::<TicketData>().id(), "You are not the winner");

            self.xrd_vault.take_all()
        }
    }
}