调用方法并取消交易时无法捕获Web3.js的合约错误

问题描述 投票:0回答:1

我有一个非常简单的 React 应用程序,它使用 Web3.js (

4.3.2
) 以及来自表单的一些数据来调用 Solidity 智能合约的方法。我试图在调用合约方法或发送交易时捕获错误,例如用户关闭钱包模式,以便显示错误消息。

这是设置代码(简化):

window.ethereum.request({ method: "eth_requestAccounts" });

const web3 = new Web3(window.ethereum);

const factoryCampaignContract = new web3.eth.Contract(CAMPAIGN_ABI, CAMPAIGN_ADDRESS);

在表单的提交处理程序中,我尝试了 3 种不同的选项来捕获错误。

try-catch

try {
  const receipt = await factoryCampaignContract.methods
    .createCampaign(minimumContribution)
    .send({ from: accounts[0] });
} catch(err) {
  console.log(err);
}

.then()
+
.catch()

factoryCampaignContract.methods
  .createCampaign(minimumContribution)
  .send({ from: accounts[0] })
  .then(receipt => console.log(receipt))
  .catch(err => console.log(err));

send()
PromiEvents

factoryCampaignContract.methods
  .createCampaign(minimumContribution)
  .send({ from: accounts[0] })
  .on('transactionHash', (hash) => console.log(hash))
  .on('confirmation', (confirmationNumber) => console.log(confirmationNumber))
  .on('receipt', (receipt) => console.log(receipt))
  .on('error', (error) => console.log(error));

一旦关闭钱包模式,我就会在控制台中看到以下错误,但实际上没有调用上面应该捕获错误的块:

Uncaught (in promise) Error: User rejected the request.
  at ap.<anonymous> (<anonymous>:7:4040)
  at Generator.next (<anonymous>)
  at u (<anonymous>:1:1048)
javascript reactjs async-await solidity web3js
1个回答
0
投票

没关系,谜团已解:

简短回答:

Phantom 的钱包中存在一个错误,导致 dApp 无法捕获错误。使用 MetaMask 再次尝试后,一切都按预期运行:

问题出在钱包方面,尤其是我正在使用的钱包,

  • 控制台仍会显示 MetaMask 扩展的内容脚本记录的错误:

    VM35:1 MetaMask - RPC Error: MetaMask Tx Signature: User denied transaction signature. {code: 4001, message: 'MetaMask Tx Signature: User denied transaction signature.'}
    
    
  • 但是,我的 React 应用程序中的

    try-catch

     会成功捕获该问题,使我能够正确处理它,并向用户显示错误消息。

所以,我在问题中分享的代码是正确的,Solidity 方面或 Web3.js 没有任何问题。

如果您遇到同样的问题,并且想知道为什么您在网上找到的解决方案甚至官方 Web3.js 文档中的示例都不起作用,只需尝试使用不同的钱包即可。

长答案:

深入挖掘以了解有关 Phantom 错误的更多信息,您会在他们的

contentScript.js

中找到这样的块:

switch (i = a ? "USE_METAMASK" : i, i) { case "ALWAYS_ASK": X_('\nObject.defineProperty(window, "ethereum", {\n value: undefined,\n writable: true,\n});\ndelete window.ethereum;\n '), X_('<VERY LONG STRING>'); break; case "USE_PHANTOM": X_('\nObject.defineProperty(window, "ethereum", {\n value: undefined,\n writable: true,\n});\ndelete window.ethereum;\n '), X_('<VERY LONG STRING>'); break; case "USE_METAMASK": X_('\nObject.defineProperty(window, "ethereum", {\n value: undefined,\n writable: true,\n});\ndelete window.ethereum;\n '), X_('<VERY LONG STRING>'); break; }
我在使用 Phantom 时在控制台中看到但无法捕获的错误来自这些 eval-ed 表达式,

这就是为什么控制台会说它来自一个名为 VM1813

 的文件。

如果您在“源”选项卡中导航到它,您会发现类似以下内容:

this.request = r => ct(this, null, function* () { var n, u; let o; try { let { method: h } = r , E = "params" in r ? (n = r.params) != null ? n : [] : [] , x = vn[h]; if (!x) throw new Error("MethodNotFound"); let g = x.request.safeParse({ ... }); if (!g.success) { ... } let P = g.data; if (o = x.response.parse(yield lt(this, Mn).send(P)), "error" in o) { // ⚠️ Here's the error we see on the console: throw new gr(o.error); } try { ... } catch (M) { console.error("event emitter error", M) } return o.result } catch (h) { // ⚠️ Which is caught and re-thrown here, as `h instanceof gr` is true: throw h instanceof gr ? h : h instanceof gt ? new gr({ code: -32e3, message: "Missing or invalid parameters." }, { method: r.method }) : h instanceof Error && h.message === "MethodNotFound" ? new gr({ code: -32601, message: "The method does not exist / is not available." }, { method: r.method }) : new gr({ code: -32603, message: "Internal JSON-RPC error." }, { method: r.method }) } });
请注意,它们大量使用生成器函数并将它们包装在另一个函数(

ct

)中以返回
Promise
。你闻到了吗?最有可能的是,在隐藏在代码中的某个地方,他们正在解决 
Promise
 并随后抛出错误。

就像这个问题一样,但由于他们的代码更复杂,主要是由于使用了生成器函数和转换为Promise

,所以在哪里不是那么明显,尤其是在查看缩小的代码时。

让我们尝试在某种程度上重现该错误:

/** * Straight out of their bundle, just renamed or removed some params to make it a bit * easier to understand. */ function ct(generatorFn) { return new Promise((resolve, reject)=>{ var u = x=>{ try { E(generatorFn.next(x)) } catch (err) { reject(err) } } , h = x=>{ try { E(generatorFn.throw(x)) } catch (err) { reject(err) } } , E = x=>x.done ? resolve(x.value) : Promise.resolve(x.value).then(u, h); E((generatorFn = generatorFn()).next()) }) } /** * This is trying to simulate `lt(this, Mn).send(P)`, which opens the wallet modal and waits * for the user to confirm/reject the transaction. Note that while this allows us to reproduce * the error I was experiencing, it's not a perfect mock of what's happening in Phantom's send() * function, as explained below. */ function ltDotSend() { return ct(function*() { // This mimics (more or less, as this is blocking code) opening the wallet and // clicking Confirm (number >= 50) or Close (number < 50): const n = parseInt(prompt('Enter a number')) || 0; if (n < 50) { // Somewhere in their minified code for the send() function, they are throwing // an error AFTER the return for this function has already been called. setTimeout(() => { throw new Error('Inner error'); }); } return yield n; }); } let request = () => ct(function*() { try { const n = yield ltDotSend(); console.log('n =', n); if (n === 'ERROR') { throw new Error('Outter error'); } return n; } catch (err) { console.log('request catch', err.message); throw err; } }); async function submitHandler() { try { const requestResult = await request(); console.log('requestResult =', requestResult); } catch (err) { // This will never be invoked: console.log('submitHandler catch', err.message); } } document.getElementById('requestBtn').addEventListener('click', submitHandler);
<button id="requestBtn">request()</button>

但是,这种模拟我所经历的相同行为的尝试与 Phantom 的实现之间存在一些差异:

  • 在我的示例中,即使抛出错误,

    Promise

    返回的
    request()
    也会解析,但在原始代码中则不然。我尝试尝试使用 
    ltDotSend()
     的实现,但错误实际上是在 
    submitHandler
    try-catch
     中捕获的,这又不是原始代码中的情况。

  • 在他们的原始代码中,如果我将其添加到控制台并再次测试我的 dApp,提交处理程序可以捕获错误,因此只需将其

    request

     函数包装在 
    async function
     中即可解决问题:

    const originalRequest = window.ethereum.request; window.ethereum.request = async (...args) => { return originalRequest(...args) }
    
    
  • 他们的

    send()

    实现比我写的假的要复杂得多,因为它实际上是与扩展后台脚本通信,所以错误可能更深入。

© www.soinside.com 2019 - 2024. All rights reserved.