Skip to content

Add docs for new CW 3.0 destination callback transfer field #280

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 63 additions & 7 deletions src/pages/ibc/extensions/callbacks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,13 @@ immediately, update the contract state to reflect the new tokens or trigger othe
receives the callbacks.
</Callout>

This is how you can implement the `ibc_destination_callback` entrypoint:
The `IbcDestinationCallbackMsg` contains the packet data and the acknowledgement. Since CosmWasm
3.0, it also has an additional `transfer` field. This field is only filled on 3.0+ chains when the
packet is a successful transfer message. The following is an example of how to implement the
`ibc_destination_callback` entrypoint using either the packet data or the new `transfer` field.

<Tabs items={['Packet data', 'transfer field (CosmWasm 3.0+)']}>
<Tabs.Tab>

<Callout>
This example uses the `ibc` crate with the `serde` feature, which provides a data type for the
Expand All @@ -214,12 +220,12 @@ pub fn ibc_destination_callback(
ensure_eq!(
msg.packet.dest.port_id,
"transfer", // transfer module uses this port by default
StdError::generic_err("only want to handle transfer packets")
StdError::msg("only want to handle transfer packets")
);
ensure_eq!(
msg.ack.data,
StdAck::success(b"\x01").to_binary(), // this is how a successful transfer ack looks
StdError::generic_err("only want to handle successful transfers")
StdError::msg("only want to handle successful transfers")
);
// At this point we know that this is a callback for a successful transfer,
// but not to whom it is going, how much and what denom.
Expand All @@ -233,7 +239,7 @@ pub fn ibc_destination_callback(
ensure_eq!(
receiver,
env.contract.address,
StdError::generic_err("only want to handle transfers to this contract")
StdError::msg("only want to handle transfers to this contract")
);

// We only care about this chain's native token in this example.
Expand All @@ -251,15 +257,15 @@ pub fn ibc_destination_callback(
ensure_eq!(
packet_data.denom,
ntrn_denom_on_source_chain,
StdError::generic_err("only want to handle NTRN tokens")
StdError::msg("only want to handle NTRN tokens")
);

// Now, we can do something with our tokens.
// For example, we could send them to some address:
let msg = BankMsg::Send {
to_address: "neutron155exr8rqjrknusllpzxdfvezxr8ddpqehj9g9d".to_string(),
// this panics if the amount is too large
amount: coins(u128::from_str(&packet_data.amount).unwrap(), "untrn"),
// Cosmos SDK currently only supports 256 bit amounts, so this should not panic.
amount: vec![Coin::new(Uint256::from_str(&packet_data.amount).unwrap(), "untrn")],
};

Ok(IbcBasicResponse::new()
Expand All @@ -275,6 +281,56 @@ pub fn ibc_destination_callback(
such that a channel upgrade does not break your contract.
</Callout>

</Tabs.Tab>

<Tabs.Tab>

<Callout>
While using the `transfer` field is much easier and more future proof, the packet data version
allows handling all kinds of IBC packets, not just ICS-20 transfers. The following code is
specific to ICS-20 transfers.
</Callout>

```rust template="core"
use std::str::FromStr;

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn ibc_destination_callback(
deps: DepsMut,
env: Env,
msg: IbcDestinationCallbackMsg,
) -> StdResult<IbcBasicResponse> {
let transfer = msg.transfer.ok_or_else(|| {
StdError::msg("Chain is running CosmWasm < 3.0 or transfer was unsuccessful")
})?;

ensure_eq!(
transfer.receiver,
env.contract.address,
StdError::msg("only want to handle transfers to this contract")
);

let amount = Coins::try_from(transfer.funds)?.amount_of("untrn");

if amount.is_zero() {
return Err(StdError::msg("Contract only accepts untrn transfers"));
}

let msg = BankMsg::Send {
to_address: "neutron155exr8rqjrknusllpzxdfvezxr8ddpqehj9g9d".to_string(),
amount: vec![Coin::new(amount, "untrn")],
};

Ok(IbcBasicResponse::new()
.add_message(msg)
.add_attribute("action", "ibc_destination_callback"))
}
```

</Tabs.Tab>

</Tabs>

As mentioned above, anyone can send you a destination callback for a packet. This means you need to
make sure that the packet and acknowledgement are what you expect them to be. For example, for a
transfer message, you need to make sure that the transfer was successful, that the receiver of the
Expand Down
Loading