You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetton(TON)代币转账交易撤销问题及合约代码咨询

Jetton代币转账回滚问题与解决方案

问题核心

向所有者为智能合约的账户转账时,流程分三步:

  1. 发送方钱包发起tokenTransfer消息
  2. 接收方钱包执行transferInternal完成代币入账
  3. 向接收方所有者合约发送tokenNotification做校验

当第三步校验失败时,仅能撤销通知操作,但代币已在第二步完成转移。此前采用合约主动转回代币的方案会产生手续费,恶意用户可通过大量无效交易消耗我方TON手续费。

解决方案:两阶段验证+ bounce机制

调整转账流程,延迟代币入账,利用TON的消息bounce特性让用户承担回滚成本,避免合约资金损失。

1. 核心修改思路

  • 暂存转账信息,验证前不修改余额
  • 给所有者合约发送带bounce的tokenNotification,验证失败时消息自动回退
  • 回滚操作使用用户初始支付的TON覆盖手续费,合约不承担成本

2. 修改后的合约代码

@interface("org.ton.jetton.wallet")
contract JettonDefaultWallet {
    const minTonsForStorage: Int = ton("0.019");
    const gasConsumption: Int = ton("0.013");
    balance: Int as coins = 0;
    owner: Address;
    master: Address;
    // 暂存待验证的转账信息
    pending_transfers: Map<Int, PendingTransfer> = {};

    struct PendingTransfer {
        amount: Int;
        from: Address;
        response_destination: Address?;
        forward_payload: Cell;
    }

    init(owner: Address, master: Address){
        self.balance = 0;
        self.owner = owner;
        self.master = master;
    }

    receive(msg: TokenTransfer){
        // 0xf8a7ea5
        let ctx: Context = context();
        require(ctx.sender == self.owner, "Invalid sender");
        // 增加手续费预留,覆盖可能的回滚操作成本
        let final: Int = (((ctx.readForwardFee() * 3 + 3 * self.gasConsumption) + self.minTonsForStorage) + msg.forward_ton_amount);
        require(ctx.value > final, "Invalid value");
        
        self.balance = (self.balance - msg.amount);
        require(self.balance >= 0, "Invalid balance");
        
        let init: StateInit = initOf JettonDefaultWallet(msg.sender, self.master);
        let wallet_address: Address = contractAddress(init);
        send(SendParameters{
                to: wallet_address,
                value: 0,
                mode: SendRemainingValue,
                bounce: true,
                body: TokenTransferInternal{ // 0x178d4519
                    query_id: msg.query_id,
                    amount: msg.amount,
                    from: self.owner,
                    response_destination: msg.response_destination,
                    forward_ton_amount: msg.forward_ton_amount,
                    forward_payload: msg.forward_payload
                }.toCell(),
                code: init.code,
                data: init.data
            }
        );
    }

    receive(msg: TokenTransferInternal){
        // 0x178d4519
        let ctx: Context = context();                
        if (ctx.sender != self.master) {
            let sinit: StateInit = initOf JettonDefaultWallet(msg.from, self.master);
            require(contractAddress(sinit) == ctx.sender, "Invalid sender!");
        }
        
        // 暂存转账信息,不立即增加余额
        let pending_id: Int = msg.query_id;
        self.pending_transfers.put(pending_id, PendingTransfer{
            amount: msg.amount,
            from: msg.from,
            response_destination: msg.response_destination,
            forward_payload: msg.forward_payload
        });

        // 发送带bounce的TokenNotification,验证失败时消息会回退
        send(SendParameters{
                to: self.owner,
                value: msg.forward_ton_amount,
                mode: SendPayGasSeparately,
                bounce: true,
                body: TokenNotification{ // 0x7362d09c
                    query_id: msg.query_id,
                    amount: msg.amount,
                    from: msg.from,
                    forward_payload: msg.forward_payload
                }.toCell()
            }
        );
    }

    // 处理验证失败的bounce消息,执行回滚
    bounced(msg: bounced<TokenNotification>){
        let pending_id: Int = msg.query_id;
        let transfer: PendingTransfer = self.pending_transfers.get(pending_id)!!;
        self.pending_transfers.delete(pending_id);

        // 将代币转回发送方钱包
        let init: StateInit = initOf JettonDefaultWallet(transfer.from, self.master);
        let sender_wallet: Address = contractAddress(init);
        send(SendParameters{
            to: sender_wallet,
            value: 0,
            mode: SendRemainingValue,
            bounce: true,
            body: TokenTransferInternal{
                query_id: msg.query_id,
                amount: transfer.amount,
                from: self.owner,
                response_destination: transfer.response_destination,
                forward_ton_amount: 0,
                forward_payload: null
            }.toCell(),
            code: init.code,
            data: init.data
        });
    }

    // 接收所有者合约的验证确认,完成最终入账
    receive(msg: TokenNotificationConfirm){
        let pending_id: Int = msg.query_id;
        let transfer: PendingTransfer = self.pending_transfers.get(pending_id)!!;
        self.pending_transfers.delete(pending_id);

        self.balance = self.balance + transfer.amount;
        require(self.balance >= 0, "Invalid balance");

        // 返还剩余TON给用户
        let ctx: Context = context();
        let msg_value: Int = self.msg_value(ctx.value);
        if (transfer.response_destination != null && msg_value > 0) {
            send(SendParameters{
                to: transfer.response_destination!!,
                value: msg_value,
                bounce: false,
                body: TokenExcesses{query_id: msg.query_id}.toCell(),
                mode: SendPayGasSeparately
            });
        }
    }

    receive(msg: TokenBurn){
        let ctx: Context = context();
        require(ctx.sender == self.owner, "Invalid sender");

        self.balance = (self.balance - msg.amount);
        require(self.balance >= 0, "Invalid balance");
        let fwd_fee: Int = ctx.readForwardFee();
        require(ctx.value > ((fwd_fee + 2 * self.gasConsumption) + self.minTonsForStorage), "Invalid value - Burn");
        
        send(SendParameters{
                to: self.master,
                value: 0,
                mode: SendRemainingValue,
                bounce: true,
                body: TokenBurnNotification{
                    query_id: msg.query_id,
                    amount: msg.amount,
                    sender: self.owner,
                    response_destination: msg.response_destination
                }.toCell()
            }
        );
    }

    fun msg_value(value: Int): Int {
        let msg_value1: Int = value;
        let ton_balance_before_msg: Int = (myBalance() - msg_value1);
        let storage_fee: Int = (self.minTonsForStorage - min(ton_balance_before_msg, self.minTonsForStorage));
        msg_value1 = (msg_value1 - (storage_fee + self.gasConsumption));
        return msg_value1;
    }

    bounced(msg: bounced<TokenTransferInternal>){
        self.balance = (self.balance + msg.amount);
    }

    bounced(msg: bounced<TokenBurnNotification>){
        self.balance = (self.balance + msg.amount);
    }

    get fun get_wallet_data(): JettonWalletData {
        return
            JettonWalletData{
                balance: self.balance,
                owner: self.owner,
                master: self.master,
                code: initOf JettonDefaultWallet(self.owner, self.master).code
            };
    }
}

3. 配套要求

  • 所有者合约需实现TokenNotification处理逻辑:验证通过时发送自定义的TokenNotificationConfirm消息;验证失败时不接收消息,让消息自动bounce回接收方钱包。
  • 可给pending_transfers添加过期清理逻辑,避免存储冗余。

内容的提问来源于stack exchange,提问作者Denys

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 16:20:10