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

使用solanaJ 1.20.2发送Solana链USDT生成签名但链上无记录排查

Solana USDT转账生成签名但链无记录的代码问题排查

问题描述

使用solanaJ 1.20.2版本在Solana主网发送USDT,签名可正常生成且无报错,但链上无法查询到该交易记录。

依赖配置

<dependency>
   <groupId>com.mmorrell</groupId>
   <artifactId>solanaj</artifactId>
   <version>1.20.2</version>
</dependency>

问题代码

public static String transferUsdt(CryptoAccount fromAccount, String toAddress, long amount) {
    RpcClient rpcClient = new RpcClient(Cluster.MAINNET);
    String senderPrivateKeyBase58 = fromAccount.getPrivateKey();
    Account senderAccount = new Account(Base58.decode(senderPrivateKeyBase58));
    try {
        PublicKey usdtPublicKey = PublicKey.valueOf("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB");
        PublicKey tokenAccountFrom = rpcClient.getApi().getTokenAccountsByOwner(senderAccount.getPublicKey(), usdtPublicKey);
        PublicKey tokenAccountTo = rpcClient.getApi().getTokenAccountsByOwner(new PublicKey(toAddress), usdtPublicKey);

        Transaction transaction = new Transaction();
        transaction.addInstruction(
                ComputeBudgetProgram.setComputeUnitPrice(80000) 
        );
        transaction.addInstruction(
                TokenProgram.transfer(
                        tokenAccountFrom,         
                        tokenAccountTo,   
                        amount, 
                        senderAccount.getPublicKey()

                )
        );

        
        String signature = rpcClient.getApi().sendTransaction(transaction, senderAccount);

        
        ConfirmedTransaction confirmedTransaction = rpcClient.getApi().getTransaction(signature);
        
        long start = System.currentTimeMillis();
        while (Objects.isNull(confirmedTransaction) && (System.currentTimeMillis() - start) < 120000) {
            Thread.sleep(5000);
            SignatureStatuses signatureStatuses = rpcClient.getApi().getSignatureStatuses(List.of(signature), true);
            if (Objects.isNull(signatureStatuses)) {
                System.out.println("transaction fail");
                break;
            }
            confirmedTransaction = rpcClient.getApi().getTransaction(signature);
        }
        if (Objects.nonNull(confirmedTransaction) && Objects.isNull(confirmedTransaction.getMeta().getErr())) {
            
            return signature;
        }
    } catch (RpcException e) {
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    return null;
}

排查出的问题点及修复方案

1. 吞掉RpcException,丢失关键错误信息

代码中catch (RpcException e)块为空,没有任何日志输出。sendTransaction返回签名不代表交易已成功提交到链上,可能只是客户端生成了签名但提交过程中出现RPC错误(比如节点拒绝、手续费异常、账户非法等),但异常被吞掉导致无法定位问题。

修复:
在RpcException捕获块中添加日志输出,打印异常详情:

catch (RpcException e) {
    System.err.println("RPC调用异常: " + e.getMessage());
    e.printStackTrace();
}

2. 代币账户获取逻辑错误

rpcClient.getApi().getTokenAccountsByOwner返回的是符合条件的代币账户列表,而非单个PublicKey。当前代码直接将返回值赋值给PublicKey变量,会导致类型不匹配,交易构造失败,自然无法上链。

修复:
正确处理代币账户列表,选择可用的发送方账户;若接收方无对应USDT代币账户,需先创建关联账户:

// 获取发送方USDT代币账户列表
List<TokenAccount> fromTokenAccounts = rpcClient.getApi().getTokenAccountsByOwner(senderAccount.getPublicKey(), usdtPublicKey);
if (fromTokenAccounts.isEmpty()) {
    System.err.println("发送方无USDT代币账户");
    return null;
}
// 取第一个可用的代币账户(可根据余额筛选)
PublicKey tokenAccountFrom = fromTokenAccounts.get(0).getPublicKey();

// 处理接收方代币账户
PublicKey toPublicKey = new PublicKey(toAddress);
List<TokenAccount> toTokenAccounts = rpcClient.getApi().getTokenAccountsByOwner(toPublicKey, usdtPublicKey);
PublicKey tokenAccountTo;

if (toTokenAccounts.isEmpty()) {
    // 接收方无USDT账户,创建关联账户
    tokenAccountTo = TokenProgram.createAssociatedTokenAccount(
            senderAccount.getPublicKey(),
            toPublicKey,
            usdtPublicKey
    );
    transaction.addInstruction(TokenProgram.createAssociatedTokenAccountInstruction(
            senderAccount.getPublicKey(),
            tokenAccountTo,
            toPublicKey,
            usdtPublicKey
    ));
} else {
    tokenAccountTo = toTokenAccounts.get(0).getPublicKey();
}

3. 交易未设置区块哈希和付费方

Solana交易必须包含最近的区块哈希才能被网络验证,当前代码创建Transaction后未设置该参数,会导致交易提交后被节点直接拒绝,无法进入打包流程。

修复:
构造交易后,获取并设置最近区块哈希及付费方:

Transaction transaction = new Transaction();
// 获取并设置最近区块哈希
RecentBlockhash recentBlockhash = rpcClient.getApi().getRecentBlockhash();
transaction.setRecentBlockhash(recentBlockhash.getBlockhash());
// 设置交易付费方
transaction.setFeePayer(senderAccount.getPublicKey());

4. 签名状态检查逻辑不准确

getSignatureStatuses即使签名无效也会返回包含错误信息的对象,不会返回null。当前判断逻辑无法正确识别交易错误状态。

修复:
检查返回的签名状态中的错误信息:

SignatureStatuses signatureStatuses = rpcClient.getApi().getSignatureStatuses(List.of(signature), true);
if (signatureStatuses != null && !signatureStatuses.getValue().isEmpty()) {
    SignatureStatus status = signatureStatuses.getValue().get(0);
    if (status.getErr() != null) {
        System.err.println("交易错误: " + status.getErr());
        break;
    }
}

5. 计算预算单价过高(非核心问题,但需优化)

ComputeBudgetProgram.setComputeUnitPrice(80000)设置的手续费单价远高于主网平均水平(通常100-500 lamports),虽不会直接导致交易不上链,但会造成不必要的手续费支出,建议调整为合理数值。

修复后的完整代码示例

public static String transferUsdt(CryptoAccount fromAccount, String toAddress, long amount) {
    RpcClient rpcClient = new RpcClient(Cluster.MAINNET);
    String senderPrivateKeyBase58 = fromAccount.getPrivateKey();
    Account senderAccount = new Account(Base58.decode(senderPrivateKeyBase58));
    try {
        PublicKey usdtPublicKey = PublicKey.valueOf("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB");
        
        // 处理发送方代币账户
        List<TokenAccount> fromTokenAccounts = rpcClient.getApi().getTokenAccountsByOwner(senderAccount.getPublicKey(), usdtPublicKey);
        if (fromTokenAccounts.isEmpty()) {
            System.err.println("发送方无可用USDT代币账户");
            return null;
        }
        PublicKey tokenAccountFrom = fromTokenAccounts.get(0).getPublicKey();
        
        // 处理接收方代币账户
        PublicKey toPublicKey = new PublicKey(toAddress);
        List<TokenAccount> toTokenAccounts = rpcClient.getApi().getTokenAccountsByOwner(toPublicKey, usdtPublicKey);
        PublicKey tokenAccountTo;
        
        Transaction transaction = new Transaction();
        // 设置区块哈希和付费方
        RecentBlockhash recentBlockhash = rpcClient.getApi().getRecentBlockhash();
        transaction.setRecentBlockhash(recentBlockhash.getBlockhash());
        transaction.setFeePayer(senderAccount.getPublicKey());
        
        if (toTokenAccounts.isEmpty()) {
            // 创建接收方关联代币账户
            tokenAccountTo = TokenProgram.createAssociatedTokenAccount(
                    senderAccount.getPublicKey(),
                    toPublicKey,
                    usdtPublicKey
            );
            transaction.addInstruction(TokenProgram.createAssociatedTokenAccountInstruction(
                    senderAccount.getPublicKey(),
                    tokenAccountTo,
                    toPublicKey,
                    usdtPublicKey
            ));
        } else {
            tokenAccountTo = toTokenAccounts.get(0).getPublicKey();
        }
        
        // 添加计算预算指令(调整为合理单价)
        transaction.addInstruction(ComputeBudgetProgram.setComputeUnitPrice(500));
        
        // 添加转账指令
        transaction.addInstruction(TokenProgram.transfer(
                tokenAccountFrom,
                tokenAccountTo,
                amount,
                senderAccount.getPublicKey()
        ));
        
        // 发送交易
        String signature = rpcClient.getApi().sendTransaction(transaction, senderAccount);
        System.out.println("交易签名: " + signature);
        
        // 等待交易确认
        ConfirmedTransaction confirmedTransaction = null;
        long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < 120000) {
            Thread.sleep(5000);
            confirmedTransaction = rpcClient.getApi().getTransaction(signature);
            if (confirmedTransaction != null) {
                if (confirmedTransaction.getMeta().getErr() == null) {
                    System.out.println("交易成功确认");
                    return signature;
                } else {
                    System.err.println("交易执行错误: " + confirmedTransaction.getMeta().getErr());
                    return null;
                }
            }
            
            // 检查签名状态
            SignatureStatuses signatureStatuses = rpcClient.getApi().getSignatureStatuses(List.of(signature), true);
            if (signatureStatuses != null && !signatureStatuses.getValue().isEmpty()) {
                SignatureStatus status = signatureStatuses.getValue().get(0);
                if (status.getErr() != null) {
                    System.err.println("交易状态错误: " + status.getErr());
                    return null;
                }
            }
        }
        
        System.err.println("交易超时未确认");
    } catch (RpcException e) {
        System.err.println("RPC调用异常: " + e.getMessage());
        e.printStackTrace();
    } catch (InterruptedException e) {
        System.err.println("线程中断异常");
        e.printStackTrace();
    }
    return null;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 20:42:19