Transfer Operations
For years, token transfers behaved like fire-and-forget mail: you put a destination address on the envelope, drop it in the box, and the protocol updates balances without asking whether the recipient can actually handle what arrives. That simplicity fueled adoption, but it also caused avoidable losses when tokens were sent to contracts that didn't implement a receiving flow.
A Safer Alternative
OP-20 introduces a safer alternative: a handshaked "safe" transfer that confirms a contract recipient can accept and process tokens before finalizing the move. If the recipient can't or won't acknowledge, the operation cleanly cancels and the funds never leave the sender.
The diagram below illustrates the execution flow of the safeTransfer() method.
The diagram below illustrates the execution flow of the safeTransferFrom() method.
Using Transfer Methods in a Contract
import { u256 } from '@btc-vision/as-bignum/assembly';
import {
Address,
Blockchain,
BytesWriter,
ReentrancyGuard,
Revert,
TransferHelper,
SELECTOR_BYTE_LENGTH,
U256_BYTE_LENGTH,
U32_BYTE_LENGTH,
ADDRESS_BYTE_LENGTH
} from '@btc-vision/btc-runtime/runtime';
@final
export class MyContract extends ReentrancyGuard {
public constructor() {
super();
}
...
// This is only to demonstrate how to call another contract method.
// The TransferHelper class already contains a safeTransfer method ready to be used.
private callSafeTransfer(token: Address, to: Address, amount: u256, data: Uint8Array) : void{
// Encode safeTransfer Calldata
// 1st parameter: selector (method to call->safeTransfer)
// 2nd parameter: to address
// 3rd parameter: amount
// 4th parameter: data
const calldata = new BytesWriter(
SELECTOR_BYTE_LENGTH +
ADDRESS_BYTE_LENGTH +
U256_BYTE_LENGTH +
U32_BYTE_LENGTH +
data.length,
);
calldata.writeSelector(TransferHelper.SAFE_TRANSFER_SELECTOR);
calldata.writeAddress(to);
calldata.writeU256(amount);
calldata.writeBytesWithLength(data);
Blockchain.call(token, calldata);
}
}import { u256 } from '@btc-vision/as-bignum/assembly';
import {
Address,
Blockchain,
BytesWriter,
ReentrancyGuard,
Revert,
TransferHelper,
SELECTOR_BYTE_LENGTH,
U256_BYTE_LENGTH,
U32_BYTE_LENGTH,
ADDRESS_BYTE_LENGTH
} from '@btc-vision/btc-runtime/runtime';
@final
export class MyContract extends ReentrancyGuard {
public constructor() {
super();
}
...
// This is only to demonstrate how to call another contract method.
// The TransferHelper class already contains a safeTransferFrom method ready to be used.
private callSafeTransferFrom(token: Address, from: Address, to: Address, amount: u256, data: Uint8Array) : void{
// Encode safeTransferFrom Calldata
// 1st parameter: selector (method to call->safeTransferFrom)
// 2nd parameter: from address
// 3rd parameter: to address
// 4th parameter: amount
// 5th parameter: data
const calldata = new BytesWriter(
SELECTOR_BYTE_LENGTH +
ADDRESS_BYTE_LENGTH * 2 +
U256_BYTE_LENGTH +
U32_BYTE_LENGTH +
data.length,
);
calldata.writeSelector(TransferHelper.SAFE_TRANSFER_FROM_SELECTOR);
calldata.writeAddress(from);
calldata.writeAddress(to);
calldata.writeU256(amount);
calldata.writeBytesWithLength(data);
Blockchain.call(token, calldata);
}
}import { u256 } from '@btc-vision/as-bignum/assembly';
import {
Address,
Blockchain,
BytesWriter,
ReentrancyGuard,
Revert,
TransferHelper,
SELECTOR_BYTE_LENGTH,
U256_BYTE_LENGTH,
U32_BYTE_LENGTH,
ADDRESS_BYTE_LENGTH
} from '@btc-vision/btc-runtime/runtime';
@final
export class MyContract extends ReentrancyGuard {
public constructor() {
super();
}
...
// This is only to demonstrate how to call another contract method.
// The TransferHelper class already contains a transfer method ready to be used.
private callTransfer(token: Address, to: Address, amount: u256) : void{
// Encode transfer Calldata
// 1st parameter: selector (method to call->transfer)
// 2nd parameter: to address
// 3rd parameter: amount
const calldata = new BytesWriter(
SELECTOR_BYTE_LENGTH +
ADDRESS_BYTE_LENGTH +
U256_BYTE_LENGTH
);
calldata.writeSelector(TransferHelper.TRANSFER_SELECTOR);
calldata.writeAddress(to);
calldata.writeU256(amount);
Blockchain.call(token, calldata);
}
}import { u256 } from '@btc-vision/as-bignum/assembly';
import {
Address,
Blockchain,
BytesWriter,
ReentrancyGuard,
Revert,
TransferHelper,
SELECTOR_BYTE_LENGTH,
U256_BYTE_LENGTH,
U32_BYTE_LENGTH,
ADDRESS_BYTE_LENGTH
} from '@btc-vision/btc-runtime/runtime';
@final
export class MyContract extends ReentrancyGuard {
public constructor() {
super();
}
...
// This is only to demonstrate how to call another contract method.
// The TransferHelper class already contains a transferFrom method ready to be used.
private callTransferFrom(token: Address, from: Address, to: Address, amount: u256) : void{
// Encode transferFrom Calldata
// 1st parameter: selector (method to call->transferFrom)
// 2nd parameter: from address
// 3rd parameter: to address
// 4th parameter: amount
const calldata = new BytesWriter(
SELECTOR_BYTE_LENGTH +
ADDRESS_BYTE_LENGTH * 2 +
U256_BYTE_LENGTH
);
calldata.writeSelector(TransferHelper.TRANSFER_SELECTOR);
calldata.writeAddress(from);
calldata.writeAddress(to);
calldata.writeU256(amount);
Blockchain.call(token, calldata);
}
}