Using MATIC with Web3 and MetaMask: "Error: Returned error: unknown account"

Using MATIC with Web3 and MetaMask: "Error: Returned error: unknown account"

我有一个服务器-客户端 DApp,我测试过它在以太坊测试网络上运行良好。但是由于 gas 费,我想使用 L2,在这种情况下我选择了 Polygon (MATIC)。基本应用程序正在向网站读取和写入文本 posts,智能合约存储它们。

我已经使用 remix.ethereum.org 在 MATIC 上成功部署,并且我可以从 Remix 向合约写入交易。在我的本地主机 Web 应用程序上,我可以读取交易,但我的写入在客户端不起作用。

这是server.js

const WEB3_PROVIDER = "https://polygon-rpc.com" 
// https://blog.polygon.technology/polygon-rpc-gateway-will-provide-a-free-high-performance-connection-to-the-polygon-pos-blockchain/

//"https://cloudflare-eth.com"; //"HTTP://127.0.0.1:7545"
if (typeof web3 !== 'undefined') {
    web3 = new Web3(web3.currentProvider);
    console.log("web3 already initialized.");
} else {
    // set the provider you want from Web3.providers
    web3 = new Web3(new Web3.providers.HttpProvider(WEB3_PROVIDER));
    console.log("New web3 object initialized.");
}

app.post('/add-post', async (req, res) => {
    const post = req.body;
    
    try {
        console.log(post);

        MyContract.methods.addNewPost(post['name'], post['post'], post['date']).send({from: post['addr'], gas:3000000}).then(function(result) {
            const output_response = "add-post successful :: ";//+String(result);
            res.send(output_response);
        }).catch(function(err) {
            const output_response = "add-post failed :: "+String(err);
            res.send(output_response);
        });
        
    } catch (e) { throw e; }
});

这里是 client.js 中的片段,我在其中添加 post,方法是获取 html 输入表单,然后传递给以下内容:

const web3 = new Web3(window.ethereum);

async function addPost(post_input) {
    stringify_post_input = JSON.stringify(post_input);
    const post_response = await fetch('/add-post', {method: 'POST', body: stringify_post_input, headers: { "content-type": "application/json" } });
    var post_response_text = await post_response.text();
    console.log(post_response_text);
}

现在这通常可以在以太坊测试网络上完美运行,我所做的只是 server.js 中的 web3 初始化。但是现在在 MATIC 网络上,我在我的客户端浏览器中得到

add-post failed :: Error: Returned error: unknown account

这让我很困惑,因为

  1. 我可以在 remix.ethereum.org 中手动添加 posts,我在其中部署了完全相同的 MyContract
  2. 我有其他服务器端调用从 MyContract 读取并且工作正常(即我可以读取现有的 post 我从 Remix 添加)。

所以我的客户只能读但不能写,即没有 MetaMask 弹出窗口要求我确认支付汽油费。

这是我第一次尝试使用 L2,所以我不知道是否所有 web3 代码都应该相同。我的印象是我只需要交换网络并登录到我的 MetaMask,一切都应该没问题。但是我对web3的理解不是很深,所以不确定。

非常感谢 - 理想情况下,当我尝试使用 MyContract.methods...() 编写时,我应该在我的客户端浏览器中看到一个 MetaMask 弹出窗口,要求我确认支付 gas 费用。

MyContract.methods.addNewPost(...).send({from: post['addr'], gas:3000000})

此代码段从post['addr']地址生成交易,并将生成的交易发送到节点。当 web3 实例持有该地址对应的私钥时,它在发送之前签署交易。

当 web3 不持有私钥时,它会发送未签名的交易。这仅在开发环境(例如 Remix VM 模拟器、Ganache、Hardhat 等)中有用,在该环境中节点可能持有私钥并仍然签署交易。但是在这种情况下,节点没有对应的私钥和returns“未知帐户”错误。


您的问题已经提出了一个解决方案 - 请求 MetaMask(或其他持有用户私钥的钱包软件)签署交易使用 web3 或在节点上对其进行签名。

这需要将交易生成逻辑移动到 前端 应用程序(在您的情况下 client.js),以便前端应用程序可以与 MetaMask [=36] 通信=] 通过 window.ethereum 对象 (docs) 可用,这在逻辑上在后端应用程序中不可用。

client.js:

// containing the contract `addPost` function definition
const abiJson = [{
    "inputs": [
        {"internalType": "string", "name": "_name", "type": "string"},
        {"internalType": "string", "name": "_post", "type": "string"},
        {"internalType": "string", "name": "_date", "type": "string"}
    ],
    "name": "addPost", "outputs": [], "stateMutability": "nonpayable", "type": "function"
}];
const contractAddress = "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF";

let accounts, web3, contract;

async function init() {
    // popup - get the user's address
    accounts = await ethereum.request({ method: 'eth_requestAccounts' });

    // using web3 just as a helper to generate the transaction
    // (see the `data` field and `encodeABI`) - not to sign it
    web3 = new Web3();
    contract = new web3.eth.Contract(abiJson);
}

async function addPost(postInput) {
    const transactionParameters = {
        from: accounts[0],
        to: contractAddress,
        data: contract.methods.addPost(
            postInput.name,
            postInput.post,
            postInput.date
        ).encodeABI(),
        gasPrice: '0x09184e72a000', // custom gas price
    };
    // popup - request the user to sign and broadcast the transaction
    await ethereum.request({
        method: 'eth_sendTransaction',
        params: [transactionParameters],
    });
}

async function run() {
    await init();
    await addPost({
        name: "This is a post name",
        post: "Hello world",
        date: "date"
    });
}

run();

现在您可以安全地删除终结点,因为它不再需要了。