---
# System prepended metadata

title: 三分钟跨链

---

# 三分钟跨链

第一步，先安装需要用到的库

```
$ forge install OpenZeppelin/openzeppelin-contracts@v4.8.3 --no-commit
$ forge install TusimaNetwork/zkBridge-messaging-interfaces --no-commit
```

第二步，实现一个TokenMock合约，用于给用户铸造代币和发送时代币销毁。

ITokenMock.sol

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.16;
import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";

interface ITokenMock is IERC20 {
    function mint(address _to, uint256 _amount) external;
    function burn(address _owner,uint256 _amount) external;
}
```

TokenMock.sol

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.16;
import {ITokenMock} from "./interface/ITokenMock.sol";
import {Ownable} from "lib/openzeppelin-contracts/contracts/access/Ownable.sol";
import {ERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";


contract TokenMock is ERC20,Ownable,ITokenMock {
    mapping (address => bool) public admin;

    constructor(
        string memory name_,
        string memory symbol_
    ) ERC20(name_, symbol_) {}


    modifier onlyAdmin() {
        require(admin[msg.sender], "Only admin allowed");
        _;
    }

    function setAdmin(address _allower,bool _isAllowed) public onlyOwner {
        admin[_allower] = _isAllowed;
    }

    function claim(address _to) public {
         _mint(_to, 1000 ether);
    }


    function mint(address _to, uint256 _amount) public virtual override onlyAdmin{
        _mint(_to, _amount);
    }

    function burn(address _owner, uint256 _amount) public virtual override onlyAdmin{
        _burn(_owner, _amount);
    }
}

```

> 在发送合约部署完成后，记得授予发送合约`allower`权限。

第三步，实现在源链的发送合约：

我们在合约初始化时写入源链的messaging合约地址和TokenMock合约地址，messaging合约地址在文档中查询，TokenMock需要自己部署并记录地址。

```solidity
ISender public messaging;
address public tokenMock;

constructor(address _tusimaMessaging, address _tokenMock) {
        messaging = ISender(_tusimaMessaging);
        tokenMock = _tokenMock;
    }
```

要实现跨链发送的操作，我们需要提供接口中需要的三个参数：

targetChainId：目标链的ID

targetAddr：在目标链的接收合约

message：需要跨链的消息

由于我们的Token跨链操作有更多的信息，比如接收者、转账数额，我们就把剩下的参数打包成bytes字段，作为message参数，在目标链接收合约中再拆解出相应的信息来。

```solidity
bytes memory message = abi.encodePacked(_tokenAmount, _receiver);
```

这样就能通过接口实现基本的跨链：

```solidity
    function sendToken(
        uint32 _destinationChainId,
        address _msgReceiver,
        uint256 _tokenAmount,
        address _receiver
    ) external {
        bytes memory message = abi.encodePacked(_tokenAmount, _receiver);
        messaging.send(_destinationChainId, _msgReceiver, message);
    }
```

我们再完善一下合约，让用户的token在发送时销毁掉，发起跨链交易成功后打印相应的事件，完整代码如下：

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.16;

import {ITokenMock} from "./interface/ITokenMock.sol";
import {ISender} from "lib/zkBridge-messaging-interfaces/src/interfaces/IMessaging.sol";

contract TokenSender {
    event SendToken(
        uint32 destinationChainId,
        address msgReceiver,
        uint256 tokenAmount,
        address receiver
    );

    ISender public messaging;
    address public tokenMock;

    constructor(address _tusimaMessaging, address _tokenMock) {
        messaging = ISender(_tusimaMessaging);
        tokenMock = _tokenMock;
    }

    function sendToken(
        uint32 _destinationChainId,
        address _msgReceiver,
        uint256 _tokenAmount,
        address _receiver
    ) external {
        ITokenMock(tokenMock).burn(msg.sender, _tokenAmount);
        bytes memory message = abi.encodePacked(_tokenAmount, _receiver);
        messaging.send(_destinationChainId, _msgReceiver, message);

        emit SendToken(
            _destinationChainId,
            _msgReceiver,
            _tokenAmount,
            _receiver
        );
    }
}
```

第四步，实现目标链接收合约

同样，我们需要先部署一个 TokenMock 合约，用于给跨链成功的用户铸造代币，并记录合约地址。

在接收合约部署完成后，同样记得授予接收合约`allower`权限。

我们在合约初始化时写入目标链的messaging合约地址和TokenMock合约地址，messaging合约地址在文档中查询，TokenMock需要自己部署并记录地址。

```solidity
ISender public messaging;
address public tokenMock;

constructor(address _tusimaMessaging, address _tokenMock) {
        messaging = ISender(_tusimaMessaging);
        tokenMock = _tokenMock;
    }
```

要实现接收合约，最重要的是实现接口中的`handleMsgImpl`函数。该函数的作用是供跨链桥合约messaging调用，将接收到的信息推送到接收合约中。

```solidity
function handleMsg(uint32 sourceChainId, address sourceAddr, bytes memory message) external
```

可以看到该函数依旧只有三个参数，对我们最重要的是 message 参数，messgae内包含了接收者地址和跨链金额信息，我们需要将它们解析出来：

```solidity=
struct Info {
    uint256 tokenAmount;
    address receiver;
}

function decodeInfo(
        bytes memory _message
    ) public pure returns (Info memory info) {
        uint256 tokenAmount; // 256 / 8 = 32
        address receiver; // 20 bytes
        assembly {
            tokenAmount := mload(add(_message, 32))
            receiver := mload(add(_message, 52))
        }
        info.tokenAmount = tokenAmount;
        info.receiver = receiver;
    }
```

随后我们就可以实现`handleMsgImpl`函数了：

```solidity
 function handleMsgImpl(
        uint32 _sourceChainId,
        address _sourceAddress,
        bytes memory _message
    ) internal override {
        Info memory info = decodeInfo(_message);
    }
```

我们添加一些必要操作，比如在这个地方给接收者铸造出来跨链成功的代币，打印出相应的事件等，完整代码如下：

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.16;
import {ITokenMock} from "./interface/ITokenMock.sol";
import {Receiver} from "lib/zkBridge-messaging-interfaces/src/interfaces/Receiver.sol";

contract TokenReceiver is Receiver {
    struct Info {
        uint256 tokenAmount;
        address receiver;
    }

    event TokenMessageReceived(
        uint32 indexed sourceChainId,
        address indexed sourceAddress,
        Info message
    );

    address public tokenMock;

    constructor(
        address _tusimaMessaging,
        address _tokenMock
    ) Receiver(_tusimaMessaging) {
        tokenMock = _tokenMock;
    }

    function handleMsgImpl(
        uint32 _sourceChainId,
        address _sourceAddress,
        bytes memory _message
    ) internal override {
        Info memory info = decodeInfo(_message);

        ITokenMock(tokenMock).mint(info.receiver, info.tokenAmount);

        emit TokenMessageReceived(_sourceChainId, _sourceAddress, info);
    }

    function decodeInfo(
        bytes memory _message
    ) public pure returns (Info memory info) {
        uint256 tokenAmount; // 256 / 8 = 32
        address receiver; // 20 bytes
        assembly {
            tokenAmount := mload(add(_message, 32))
            receiver := mload(add(_message, 52))
        }
        info.tokenAmount = tokenAmount;
        info.receiver = receiver;
    }
}
```

