Whether claim address offline will be the prevalent use case aside, I think EarmarkedLibraCoin can still work just that one’d be earmarking for an authentication key rather than address, which just happen to be the same in current implementation.
Below is my attempt to earmark for both auth key (for proof of ownership) and, let’s call it vanity address (for human readability). Note that I assumed a get_txn_pub_key()
to get the public key that’s associated with every transaction instead of passing it in as a parameter, this is safer since we can rely on Admission Control to guarantee that the public key matches signature in said transaction.
If the recipient failed to acquire the intended vanity address, the creator can simply claim the Libra coins and re-earmark. If this is too much hassle, one can provide simplified overloads that ignores vanity address and just earmark/assert against authentication key, only losing some convenience in human readability.
// A module for earmarking a coin for a specific recipient
module EarmarkedLibraCoin {
import 0x0.LibraCoin;
// A wrapper containing a Libra coin and the address of the recipient the
// coin is earmarked for.
resource T {
coin: R#LibraCoin.T,
recipient: address,
authentication_key: bytes
}
// Create a new earmarked coin with the given `recipient`.
// Publish the coin under the transaction sender's account address.
public create(coin: R#LibraCoin.T, recipient: address, authentication_key: bytes) {
let t: R#Self.T;
// Construct or "pack" a new resource of type T. Only procedures of the
// `EarmarkedLibraCoin` module can create an `EarmarkedLibraCoin.T`.
t = T {
coin: move(coin),
recipient: move(recipient),
authentication_key: move(authentication_key)
};
// Publish the earmarked coin under the transaction sender's account
// address. Each account can contain at most one resource of a given type;
// this call will fail if the sender already has a resource of this type.
move_to_sender<T>(move(t));
return;
}
// Allow the transaction sender to claim a coin that was earmarked for her.
public claim_for_recipient(earmarked_coin_address: address): R#Self.T {
let t: R#Self.T;
let t_ref: &R#Self.T;
let sender: address;
// Remove the earmarked coin resource published under `earmarked_coin_address`.
// If there is no resource of type T published under the address, this will fail.
t = move_from<T>(move(earmarked_coin_address));
t_ref = &t;
// This is a builtin that returns the address of the transaction sender.
sender = get_txn_sender();
pub_key = get_txn_pub_key();
// Ensure that the transaction sender is the recipient. If this assertion
// fails, the transaction will fail and none of its effects (e.g.,
// removing the earmarked coin) will be committed. 99 is an error code
// that will be emitted in the transaction output if the assertion fails.
assert(*(&move(t_ref).recipient) == move(sender), 99);
assert(*(&move(t_ref).authentication_key) == sha3(move(pub_key)), 99)
return move(t);
}
// Allow the creator of the earmarked coin to reclaim it.
public claim_for_creator(): R#Self.T {
let t: R#Self.T;
let sender: address;
sender = get_txn_sender();
// This will fail if no resource of type T under the sender's address.
t = move_from<T>(move(sender));
return move(t);
}
// Extract the Libra coin from its wrapper and return it to the caller.
public unwrap(t: R#Self.T): R#LibraCoin.T {
let coin: R#LibraCoin.T;
let recipient: address;
// This "unpacks" a resource type by destroying the outer resource, but
// returning its contents. Only the module that declares a resource type
// can unpack it.
T { coin, recipient, authentication_key } = move(t);
return move(coin);
}
}