diff --git a/typescript/.changeset/quiet-pugs-repeat.md b/typescript/.changeset/quiet-pugs-repeat.md new file mode 100644 index 000000000..c13c90a9f --- /dev/null +++ b/typescript/.changeset/quiet-pugs-repeat.md @@ -0,0 +1,5 @@ +--- +"@coinbase/agentkit": patch +--- + +Fixed a crash on wallet provider initialization when the analytics request fails diff --git a/typescript/agentkit/src/wallet-providers/walletProvider.test.ts b/typescript/agentkit/src/wallet-providers/walletProvider.test.ts index 503961dc7..fc16a790d 100644 --- a/typescript/agentkit/src/wallet-providers/walletProvider.test.ts +++ b/typescript/agentkit/src/wallet-providers/walletProvider.test.ts @@ -140,6 +140,32 @@ describe("WalletProvider", () => { ); }); + it("should handle tracking rejections gracefully", () => { + (sendAnalyticsEvent as jest.Mock).mockImplementationOnce(() => + Promise.reject(new Error("HTTP error! status: 400")), + ); + + const consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(() => {}); + const unhandledRejectionSpy = jest.fn(); + process.on("unhandledRejection", unhandledRejectionSpy); + + new MockWalletProvider(); + + return new Promise(resolve => + setTimeout(() => { + expect(consoleWarnSpy).toHaveBeenCalledWith( + "Failed to track wallet provider initialization:", + expect.any(Error), + ); + expect(unhandledRejectionSpy).not.toHaveBeenCalled(); + + process.off("unhandledRejection", unhandledRejectionSpy); + consoleWarnSpy.mockRestore(); + resolve(null); + }, 0), + ); + }); + it("should convert wallet provider to signer", () => { const provider = new MockWalletProvider(); const signer = provider.toSigner(); diff --git a/typescript/agentkit/src/wallet-providers/walletProvider.ts b/typescript/agentkit/src/wallet-providers/walletProvider.ts index 9e523c67c..ad7ea70df 100644 --- a/typescript/agentkit/src/wallet-providers/walletProvider.ts +++ b/typescript/agentkit/src/wallet-providers/walletProvider.ts @@ -21,7 +21,14 @@ export abstract class WalletProvider { * Tracks the initialization of the wallet provider. */ private trackInitialization() { + const onError = (error: unknown) => + console.warn("Failed to track wallet provider initialization:", error); + try { + // `sendAnalyticsEvent` is async and rejects on a non-ok response or a + // network failure. The surrounding try/catch only catches synchronous + // throws, so the rejection has to be handled here. Otherwise it becomes + // an unhandled rejection, which terminates the host process on Node 15+. sendAnalyticsEvent({ name: "agent_initialization", action: "initialize_wallet_provider", @@ -31,9 +38,9 @@ export abstract class WalletProvider { network_id: this.getNetwork().networkId, chain_id: this.getNetwork().chainId, protocol_family: this.getNetwork().protocolFamily, - }); + }).catch(onError); } catch (error) { - console.warn("Failed to track wallet provider initialization:", error); + onError(error); } }