From ae5570e48f8e9ae222849d907c125624d075ff1a Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 10:03:33 +0200 Subject: [PATCH 01/20] feat: open WS connections onActive --- package.json | 2 +- packages/snap/package.json | 4 +- packages/snap/snap.manifest.json | 4 +- .../WebSocketConnectionService.ts | 20 +-- packages/snap/src/index.ts | 19 ++- yarn.lock | 151 ++++++++++++------ 6 files changed, 134 insertions(+), 66 deletions(-) diff --git a/package.json b/package.json index 53a0ad241..b9647ef9e 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ ] }, "resolutions": { - "@metamask/snaps-sdk": "9.0.0" + "@metamask/snaps-sdk": "9.3.0" }, "devDependencies": { "@commitlint/cli": "^17.7.1", diff --git a/packages/snap/package.json b/packages/snap/package.json index 1643ca5da..8980882c7 100644 --- a/packages/snap/package.json +++ b/packages/snap/package.json @@ -44,8 +44,8 @@ "@metamask/keyring-api": "^18.0.0", "@metamask/keyring-snap-sdk": "^4.0.0", "@metamask/snaps-cli": "^8.1.1", - "@metamask/snaps-jest": "9.3.0", - "@metamask/snaps-sdk": "^9.2.0", + "@metamask/snaps-jest": "9.4.0", + "@metamask/snaps-sdk": "^9.3.0", "@metamask/superstruct": "^3.1.0", "@metamask/utils": "11.4.0", "@noble/ed25519": "2.1.0", diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index fd66a6c0c..fd8a50522 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "W86v0OUr3ogUxyukFcAgbTjaqxtra+nN67DvfgLuLJ8=", + "shasum": "KJzrCyRnU39OGKoKvSslXwR5+OhNVAgugtQxZhrzMkk=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -90,6 +90,6 @@ "snap_dialog": {}, "snap_getPreferences": {} }, - "platformVersion": "9.0.0", + "platformVersion": "9.3.0", "manifestVersion": "0.1" } diff --git a/packages/snap/src/core/services/subscriptions/WebSocketConnectionService.ts b/packages/snap/src/core/services/subscriptions/WebSocketConnectionService.ts index fd392db2a..ca79120b2 100644 --- a/packages/snap/src/core/services/subscriptions/WebSocketConnectionService.ts +++ b/packages/snap/src/core/services/subscriptions/WebSocketConnectionService.ts @@ -69,10 +69,11 @@ export class WebSocketConnectionService { } #bindHandlers(): void { - // When the extension starts, or that the snap is updated / installed, the Snap platform has lost all its previously opened websockets, so we need to re-initialize - this.#eventEmitter.on('onStart', this.#handleOnStart.bind(this)); - this.#eventEmitter.on('onUpdate', this.#handleOnStart.bind(this)); - this.#eventEmitter.on('onInstall', this.#handleOnStart.bind(this)); + // When the extension becomes active, starts, or that the snap is updated / installed, the Snap platform might have lost its previously opened websockets, so we make sure the are open + this.#eventEmitter.on('onStart', this.#setupConnections.bind(this)); + this.#eventEmitter.on('onUpdate', this.#setupConnections.bind(this)); + this.#eventEmitter.on('onInstall', this.#setupConnections.bind(this)); + this.#eventEmitter.on('onActive', this.#setupConnections.bind(this)); this.#eventEmitter.on( 'onWebSocketEvent', @@ -83,8 +84,8 @@ export class WebSocketConnectionService { this.#eventEmitter.on('onListWebSockets', this.#listConnections.bind(this)); } - async #handleOnStart(): Promise { - this.#logger.log(`Handling onStart/onUpdate/onInstall`); + async #setupConnections(): Promise { + this.#logger.log(`Setting up connections`); const { activeNetworks } = this.#configProvider.get(); @@ -99,9 +100,10 @@ export class WebSocketConnectionService { } /** - * Opens a connection for the specified network. - * @param network - The network to open a connection for. - * @returns A promise that resolves to the connection. + * Open a WebSocket connection for the given network. + * If a connection already exists for the network, this method does nothing. + * @param network - The network for which to open a connection. + * @returns A promise that resolves when the connection is established or already exists. */ async openConnection(network: Network): Promise { this.#logger.log(`Opening connection for network ${network}`); diff --git a/packages/snap/src/index.ts b/packages/snap/src/index.ts index 768525460..0f8bd135d 100644 --- a/packages/snap/src/index.ts +++ b/packages/snap/src/index.ts @@ -2,12 +2,14 @@ import { KeyringRpcMethod } from '@metamask/keyring-api'; import { handleKeyringRequest } from '@metamask/keyring-snap-sdk'; import type { Json, + OnActiveHandler, OnAssetHistoricalPriceHandler, OnAssetsConversionHandler, OnAssetsLookupHandler, OnAssetsMarketDataHandler, OnClientRequestHandler, OnCronjobHandler, + OnInactiveHandler, OnInstallHandler, OnKeyringRequestHandler, OnNameLookupHandler, @@ -49,7 +51,6 @@ import snapContext, { clientRequestHandler, eventEmitter, keyring, - state, } from './snapContext'; installPolyfills(); @@ -260,6 +261,10 @@ export const onWebSocketEvent: OnWebSocketEventHandler = async ({ event }) => await eventEmitter.emitSync('onWebSocketEvent', event); }); +/* + * Lifecycle handlers + */ + export const onStart: OnStartHandler = async () => withCatchAndThrowSnapError(async () => { await eventEmitter.emitSync('onStart'); @@ -275,6 +280,18 @@ export const onInstall: OnInstallHandler = async () => await eventEmitter.emitSync('onInstall'); }); +export const onActive: OnActiveHandler = async () => { + return withCatchAndThrowSnapError(async () => { + await eventEmitter.emitSync('onActive'); + }); +}; + +export const onInactive: OnInactiveHandler = async () => { + return withCatchAndThrowSnapError(async () => { + await eventEmitter.emitSync('onInactive'); + }); +}; + export const onNameLookup: OnNameLookupHandler = async (request) => { const result = await withCatchAndThrowSnapError(async () => onNameLookupHandler(request), diff --git a/yarn.lock b/yarn.lock index 00038a0aa..f8cc1f006 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3895,14 +3895,13 @@ __metadata: languageName: node linkType: hard -"@metamask/controller-utils@npm:^11.10.0": - version: 11.10.0 - resolution: "@metamask/controller-utils@npm:11.10.0" +"@metamask/controller-utils@npm:^11.11.0": + version: 11.11.0 + resolution: "@metamask/controller-utils@npm:11.11.0" dependencies: - "@ethereumjs/util": ^9.1.0 "@metamask/eth-query": ^4.0.0 "@metamask/ethjs-unit": ^0.3.0 - "@metamask/utils": ^11.2.0 + "@metamask/utils": ^11.4.2 "@spruceid/siwe-parser": 2.1.0 "@types/bn.js": ^5.1.5 bignumber.js: ^9.1.2 @@ -3910,9 +3909,10 @@ __metadata: cockatiel: ^3.1.2 eth-ens-namehash: ^2.0.8 fast-deep-equal: ^3.1.3 + lodash: ^4.17.21 peerDependencies: "@babel/runtime": ^7.0.0 - checksum: b6b1b3ed6b963a21dcb6c0eb9779a4e4d66b6dab7459cee18fc75db7e66a8b717e6b3095a1a981f102321ff9ebcb7eb2e842af863ef546334bd6b602e61fd77e + checksum: 2bbddc5ce21615fd07ce46a71c55fabdb2a83525807377149e20aa2f92c500b2d5534012cd16c640086cc16cd482a0997f308facd88fc1145957dcad52b1bf7e languageName: node linkType: hard @@ -4263,18 +4263,18 @@ __metadata: languageName: node linkType: hard -"@metamask/phishing-controller@npm:^12.6.0": - version: 12.6.0 - resolution: "@metamask/phishing-controller@npm:12.6.0" +"@metamask/phishing-controller@npm:^13.1.0": + version: 13.1.0 + resolution: "@metamask/phishing-controller@npm:13.1.0" dependencies: "@metamask/base-controller": ^8.0.1 - "@metamask/controller-utils": ^11.10.0 + "@metamask/controller-utils": ^11.11.0 "@noble/hashes": ^1.4.0 "@types/punycode": ^2.1.0 ethereum-cryptography: ^2.1.2 fastest-levenshtein: ^1.0.16 punycode: ^2.1.1 - checksum: 76f65c39e290daf2726c73b628c29dab01d24ae51e4ffb101decf62b38927cee9088180fd2799f652030d490e70be2d1aa7269a56d1959ec642ef9fd725598fb + checksum: a3239c76cec3821808deeb55bfe53d550d6018b64ae6318af19d973cc2cf3453223d94aadb3d91d28bf7c7afb7f24d9a0664e420f28bece904566d974371f2d2 languageName: node linkType: hard @@ -4437,9 +4437,9 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-controllers@npm:^14.1.0": - version: 14.1.0 - resolution: "@metamask/snaps-controllers@npm:14.1.0" +"@metamask/snaps-controllers@npm:^14.2.0": + version: 14.2.0 + resolution: "@metamask/snaps-controllers@npm:14.2.0" dependencies: "@metamask/approval-controller": ^7.1.3 "@metamask/base-controller": ^8.0.1 @@ -4448,13 +4448,13 @@ __metadata: "@metamask/key-tree": ^10.1.1 "@metamask/object-multiplex": ^2.1.0 "@metamask/permission-controller": ^11.0.6 - "@metamask/phishing-controller": ^12.6.0 + "@metamask/phishing-controller": ^13.1.0 "@metamask/post-message-stream": ^10.0.0 "@metamask/rpc-errors": ^7.0.3 "@metamask/snaps-registry": ^3.2.3 - "@metamask/snaps-rpc-methods": ^13.3.0 - "@metamask/snaps-sdk": ^9.2.0 - "@metamask/snaps-utils": ^11.1.0 + "@metamask/snaps-rpc-methods": ^13.4.0 + "@metamask/snaps-sdk": ^9.3.0 + "@metamask/snaps-utils": ^11.2.0 "@metamask/utils": ^11.4.2 "@xstate/fsm": ^2.0.0 async-mutex: ^0.5.0 @@ -4470,49 +4470,49 @@ __metadata: semver: ^7.5.4 tar-stream: ^3.1.7 peerDependencies: - "@metamask/snaps-execution-environments": ^10.1.0 + "@metamask/snaps-execution-environments": ^10.2.0 peerDependenciesMeta: "@metamask/snaps-execution-environments": optional: true - checksum: 4e8bd5797d6c054c67e35cab114ef256517428059b8ab80d4b81e6b599bbeaff5322de1025d0a972f71fa8978cb7c4252f0c1473fab8d7ef21b28b9d5317c46c + checksum: 9fb05a4c58b1a1baf15f6c6b891ba12b8ad9597bc4d84dc27da94e75fde2244818baf887377c66cfc654f72bd890db9a0c1549983d15b9673bec812201a502e4 languageName: node linkType: hard -"@metamask/snaps-execution-environments@npm:^10.1.0": - version: 10.1.0 - resolution: "@metamask/snaps-execution-environments@npm:10.1.0" +"@metamask/snaps-execution-environments@npm:^10.2.0": + version: 10.2.0 + resolution: "@metamask/snaps-execution-environments@npm:10.2.0" dependencies: "@metamask/json-rpc-engine": ^10.0.2 "@metamask/object-multiplex": ^2.1.0 "@metamask/post-message-stream": ^10.0.0 "@metamask/providers": ^22.1.0 "@metamask/rpc-errors": ^7.0.3 - "@metamask/snaps-sdk": ^9.2.0 - "@metamask/snaps-utils": ^11.1.0 + "@metamask/snaps-sdk": ^9.3.0 + "@metamask/snaps-utils": ^11.2.0 "@metamask/superstruct": ^3.2.1 "@metamask/utils": ^11.4.2 readable-stream: ^3.6.2 - checksum: 1b417362ad8904c2d98092d6aef3e9af171fd7e6442aae637b743865cade82d4f2ade7f2aade8bebe83a997ac831194eca67f6c3a629c1b058c842b50d6462ad + checksum: 518867739ad7e9fb4d88b86f5cc6a37bd7f9d0689895486183fc2606c037d4af336476d1c3d91452d33a57eb0b6c53f63750ae2e34d2f7252c3d81b72f6a79fd languageName: node linkType: hard -"@metamask/snaps-jest@npm:9.3.0": - version: 9.3.0 - resolution: "@metamask/snaps-jest@npm:9.3.0" +"@metamask/snaps-jest@npm:9.4.0": + version: 9.4.0 + resolution: "@metamask/snaps-jest@npm:9.4.0" dependencies: "@jest/environment": ^29.5.0 "@jest/expect": ^29.5.0 "@jest/globals": ^29.5.0 - "@metamask/snaps-controllers": ^14.1.0 - "@metamask/snaps-sdk": ^9.2.0 - "@metamask/snaps-simulation": ^3.3.0 + "@metamask/snaps-controllers": ^14.2.0 + "@metamask/snaps-sdk": ^9.3.0 + "@metamask/snaps-simulation": ^3.4.0 "@metamask/superstruct": ^3.2.1 "@metamask/utils": ^11.4.2 express: ^5.1.0 jest-environment-node: ^29.5.0 jest-matcher-utils: ^29.5.0 redux: ^4.2.1 - checksum: de519a8da49c4cb7def5e0674949e7759d0788001ee7611f8fbb1024c619ff10f3463122645ccad236bb9ddca6e4e332e8039658a5a5b7f3611efe0435434802 + checksum: 4f0be1a02eadad5a5917fec98335dd9773358213f43f0d96dfbcc9c815bc8bab75cf65f4712dc8260bd85e55edc16fcb3990e3242e8e9a9ae3c9a2f573416ecc languageName: node linkType: hard @@ -4560,6 +4560,22 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-rpc-methods@npm:^13.4.0, @metamask/snaps-rpc-methods@npm:^13.5.0": + version: 13.5.0 + resolution: "@metamask/snaps-rpc-methods@npm:13.5.0" + dependencies: + "@metamask/key-tree": ^10.1.1 + "@metamask/permission-controller": ^11.0.6 + "@metamask/rpc-errors": ^7.0.3 + "@metamask/snaps-sdk": ^9.3.0 + "@metamask/snaps-utils": ^11.3.0 + "@metamask/superstruct": ^3.2.1 + "@metamask/utils": ^11.4.2 + "@noble/hashes": ^1.7.1 + checksum: 1a58cc0c1434da1f603dbcd28693aedc1476940a91e4851ac13c2624f3adce4db0f5ff56eace1da605e8cf04da319fba949c83630213fbbc0a1c1ab4f30206b8 + languageName: node + linkType: hard + "@metamask/snaps-sandbox@npm:^1.0.0": version: 1.0.0 resolution: "@metamask/snaps-sandbox@npm:1.0.0" @@ -4567,22 +4583,22 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-sdk@npm:9.0.0": - version: 9.0.0 - resolution: "@metamask/snaps-sdk@npm:9.0.0" +"@metamask/snaps-sdk@npm:9.3.0": + version: 9.3.0 + resolution: "@metamask/snaps-sdk@npm:9.3.0" dependencies: "@metamask/key-tree": ^10.1.1 "@metamask/providers": ^22.1.0 - "@metamask/rpc-errors": ^7.0.2 + "@metamask/rpc-errors": ^7.0.3 "@metamask/superstruct": ^3.2.1 - "@metamask/utils": ^11.4.0 - checksum: b9031414f3ad1f3a7e9c9fb16731f01016bfcd9206f97d6594a78a35a71118c391991a1f73553480de313c31e95bcb8762c998055d7f62a1f86769153ae5e496 + "@metamask/utils": ^11.4.2 + checksum: 0ddffc266c3802ab97724af94d07356ac8e55d91030d3f246e5f2c9154c61e8eae6f0b320cafd9bd8eca245d83c5603d0aae8c7426ed12496d512728970f4153 languageName: node linkType: hard -"@metamask/snaps-simulation@npm:^3.3.0": - version: 3.3.0 - resolution: "@metamask/snaps-simulation@npm:3.3.0" +"@metamask/snaps-simulation@npm:^3.4.0": + version: 3.4.0 + resolution: "@metamask/snaps-simulation@npm:3.4.0" dependencies: "@metamask/base-controller": ^8.0.1 "@metamask/eth-json-rpc-middleware": ^17.0.1 @@ -4590,20 +4606,21 @@ __metadata: "@metamask/json-rpc-middleware-stream": ^8.0.7 "@metamask/key-tree": ^10.1.1 "@metamask/permission-controller": ^11.0.6 - "@metamask/phishing-controller": ^12.6.0 - "@metamask/snaps-controllers": ^14.1.0 - "@metamask/snaps-execution-environments": ^10.1.0 - "@metamask/snaps-rpc-methods": ^13.3.0 - "@metamask/snaps-sdk": ^9.2.0 - "@metamask/snaps-utils": ^11.1.0 + "@metamask/phishing-controller": ^13.1.0 + "@metamask/snaps-controllers": ^14.2.0 + "@metamask/snaps-execution-environments": ^10.2.0 + "@metamask/snaps-rpc-methods": ^13.5.0 + "@metamask/snaps-sdk": ^9.3.0 + "@metamask/snaps-utils": ^11.3.0 "@metamask/superstruct": ^3.2.1 "@metamask/utils": ^11.4.2 "@reduxjs/toolkit": ^1.9.5 fast-deep-equal: ^3.1.3 + immer: ^9.0.21 mime: ^3.0.0 readable-stream: ^3.6.2 redux-saga: ^1.2.3 - checksum: af2043d12779d9182c61eb5e4d3c4836ddc479a633f2efecc5df149d1abe6d681689f58e1e5f752565f1e50ac77753acd2f62f9950e62318a052357c3f431d38 + checksum: 4e4bf01eb626063076c4f83d7b1eaeffc9d27881fd4729a573b101626146aa9d2fc92632101c3439c889a00021576eaf70644a7c484743351cf9efbf2c771ed6 languageName: node linkType: hard @@ -4671,6 +4688,38 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-utils@npm:^11.2.0, @metamask/snaps-utils@npm:^11.3.0": + version: 11.3.0 + resolution: "@metamask/snaps-utils@npm:11.3.0" + dependencies: + "@babel/core": ^7.23.2 + "@babel/types": ^7.23.0 + "@metamask/base-controller": ^8.0.1 + "@metamask/key-tree": ^10.1.1 + "@metamask/permission-controller": ^11.0.6 + "@metamask/rpc-errors": ^7.0.3 + "@metamask/slip44": ^4.2.0 + "@metamask/snaps-registry": ^3.2.3 + "@metamask/snaps-sdk": ^9.3.0 + "@metamask/superstruct": ^3.2.1 + "@metamask/utils": ^11.4.2 + "@noble/hashes": ^1.7.1 + "@scure/base": ^1.1.1 + chalk: ^4.1.2 + cron-parser: ^4.5.0 + fast-deep-equal: ^3.1.3 + fast-json-stable-stringify: ^2.1.0 + fast-xml-parser: ^4.4.1 + luxon: ^3.5.0 + marked: ^12.0.1 + rfdc: ^1.3.0 + semver: ^7.5.4 + ses: ^1.13.1 + validate-npm-package-name: ^5.0.0 + checksum: f1d173e5856674ed036940a27748deff485db1677a09ea7f3556a80644ec0bb5200c1de2e1963eaa30d7807203a1f7bc66bdb8da9d3c1952f2b4505ee3365e81 + languageName: node + linkType: hard + "@metamask/snaps-webpack-plugin@npm:^5.0.0": version: 5.0.0 resolution: "@metamask/snaps-webpack-plugin@npm:5.0.0" @@ -4695,8 +4744,8 @@ __metadata: "@metamask/keyring-api": ^18.0.0 "@metamask/keyring-snap-sdk": ^4.0.0 "@metamask/snaps-cli": ^8.1.1 - "@metamask/snaps-jest": 9.3.0 - "@metamask/snaps-sdk": ^9.2.0 + "@metamask/snaps-jest": 9.4.0 + "@metamask/snaps-sdk": ^9.3.0 "@metamask/superstruct": ^3.1.0 "@metamask/utils": 11.4.0 "@noble/ed25519": 2.1.0 From 5cc3e6f5c7ab7f8ba27dd1e4e0c2fd1bf097d24c Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 10:04:50 +0200 Subject: [PATCH 02/20] docs: jsdoc --- .../core/services/subscriptions/WebSocketConnectionService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/snap/src/core/services/subscriptions/WebSocketConnectionService.ts b/packages/snap/src/core/services/subscriptions/WebSocketConnectionService.ts index ca79120b2..03539c395 100644 --- a/packages/snap/src/core/services/subscriptions/WebSocketConnectionService.ts +++ b/packages/snap/src/core/services/subscriptions/WebSocketConnectionService.ts @@ -100,7 +100,7 @@ export class WebSocketConnectionService { } /** - * Open a WebSocket connection for the given network. + * Idempotently opens a WebSocket connection for the given network. * If a connection already exists for the network, this method does nothing. * @param network - The network for which to open a connection. * @returns A promise that resolves when the connection is established or already exists. From 53bed83c0ab397370a41bfaf02d3e754f824fdc5 Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 11:24:52 +0200 Subject: [PATCH 03/20] chore: platform version --- packages/snap/snap.manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index fd8a50522..e81c718c5 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -90,6 +90,6 @@ "snap_dialog": {}, "snap_getPreferences": {} }, - "platformVersion": "9.3.0", + "platformVersion": "9.2.0", "manifestVersion": "0.1" } From c411d86dc46a2c6c2422f02128b38c4e99e999cc Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 11:39:28 +0200 Subject: [PATCH 04/20] fix: never remove native asset --- packages/snap/package.json | 2 +- packages/snap/snap.manifest.json | 4 ++-- packages/snap/src/core/services/assets/AssetsService.ts | 7 ++++++- yarn.lock | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/snap/package.json b/packages/snap/package.json index 8980882c7..bb4955e48 100644 --- a/packages/snap/package.json +++ b/packages/snap/package.json @@ -45,7 +45,7 @@ "@metamask/keyring-snap-sdk": "^4.0.0", "@metamask/snaps-cli": "^8.1.1", "@metamask/snaps-jest": "9.4.0", - "@metamask/snaps-sdk": "^9.3.0", + "@metamask/snaps-sdk": "^9.2.0", "@metamask/superstruct": "^3.1.0", "@metamask/utils": "11.4.0", "@noble/ed25519": "2.1.0", diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index e81c718c5..b99368147 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "KJzrCyRnU39OGKoKvSslXwR5+OhNVAgugtQxZhrzMkk=", + "shasum": "7nm8j6V5cWDBiNdllUuCoSbcLg7ixxny2YeSaQrFzpQ=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -90,6 +90,6 @@ "snap_dialog": {}, "snap_getPreferences": {} }, - "platformVersion": "9.2.0", + "platformVersion": "9.3.0", "manifestVersion": "0.1" } diff --git a/packages/snap/src/core/services/assets/AssetsService.ts b/packages/snap/src/core/services/assets/AssetsService.ts index e8427c33a..130430c88 100644 --- a/packages/snap/src/core/services/assets/AssetsService.ts +++ b/packages/snap/src/core/services/assets/AssetsService.ts @@ -512,6 +512,9 @@ export class AssetsService { return savedAsset && hasZeroRawAmount(savedAsset); }; + const isNativeAsset = (asset: AssetEntity) => + asset.assetType.includes(SolanaCaip19Tokens.SOL); + const assetListUpdatedPayload = assets.reduce< AccountAssetListUpdatedEvent['params']['assets'] >( @@ -527,7 +530,9 @@ export class AssetsService { ], removed: [ ...(acc[asset.keyringAccountId]?.removed ?? []), - ...(hasZeroRawAmount(asset) ? [asset.assetType] : []), + ...(hasZeroRawAmount(asset) && !isNativeAsset(asset) // Never remove native assets from the account asset list + ? [asset.assetType] + : []), ], }, }), diff --git a/yarn.lock b/yarn.lock index f8cc1f006..f261e4666 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4745,7 +4745,7 @@ __metadata: "@metamask/keyring-snap-sdk": ^4.0.0 "@metamask/snaps-cli": ^8.1.1 "@metamask/snaps-jest": 9.4.0 - "@metamask/snaps-sdk": ^9.3.0 + "@metamask/snaps-sdk": ^9.2.0 "@metamask/superstruct": ^3.1.0 "@metamask/utils": 11.4.0 "@noble/ed25519": 2.1.0 From bacff605c6ffe01da90cf5075d374cf2572f8af3 Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 11:24:27 +0200 Subject: [PATCH 05/20] feat: move refreshSend to background event --- packages/snap/snap.manifest.json | 17 +-- .../ScheduleBackgroundEventMethod.ts | 2 + .../onCronjob/backgroundEvents/index.ts | 2 + .../backgroundEvents/refreshSend.tsx | 132 ++++++++++++++++++ .../onCronjob/cronjobs/CronjobMethod.ts | 1 - .../core/handlers/onCronjob/cronjobs/index.ts | 2 - .../onCronjob/cronjobs/refreshSend.tsx | 102 -------------- packages/snap/src/features/send/render.tsx | 6 + 8 files changed, 149 insertions(+), 115 deletions(-) create mode 100644 packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx delete mode 100644 packages/snap/src/core/handlers/onCronjob/cronjobs/refreshSend.tsx diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index b99368147..f1903fd0d 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "7nm8j6V5cWDBiNdllUuCoSbcLg7ixxny2YeSaQrFzpQ=", + "shasum": "KFKDWwTS+AdnzysJ0rV7mBuR2o81k6KHehDJlhmxWxE=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -19,7 +19,8 @@ "locales": ["locales/en.json"] }, "initialConnections": { - "https://portfolio.metamask.io": {} + "https://portfolio.metamask.io": {}, + "http://localhost:3000": {} }, "initialPermissions": { "endowment:rpc": { @@ -27,7 +28,10 @@ "snaps": false }, "endowment:keyring": { - "allowedOrigins": ["https://portfolio.metamask.io"] + "allowedOrigins": [ + "https://portfolio.metamask.io", + "http://localhost:3000" + ] }, "snap_getBip32Entropy": [ { @@ -39,13 +43,6 @@ "endowment:network-access": {}, "endowment:cronjob": { "jobs": [ - { - "duration": "PT30S", - "request": { - "method": "refreshSend", - "params": {} - } - }, { "duration": "PT20S", "request": { diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/ScheduleBackgroundEventMethod.ts b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/ScheduleBackgroundEventMethod.ts index 807b9cb4b..82747a0e5 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/ScheduleBackgroundEventMethod.ts +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/ScheduleBackgroundEventMethod.ts @@ -7,4 +7,6 @@ export enum ScheduleBackgroundEventMethod { OnTransactionRejected = 'onTransactionRejected', /** Use it to schedule a background event to asynchronously fetch the transactions of an account */ OnSyncAccountTransactions = 'onSyncAccountTransactions', + /** Use it to schedule a background event to refresh the send form */ + RefreshSend = 'refreshSend', } diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/index.ts b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/index.ts index cd4e1282b..25afa4047 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/index.ts +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/index.ts @@ -4,6 +4,7 @@ import { onSyncAccountTransactions } from './onSyncAccountTransactions'; import { onTransactionAdded } from './onTransactionAdded'; import { onTransactionApproved } from './onTransactionApproved'; import { onTransactionRejected } from './onTransactionRejected'; +import { refreshSend } from './refreshSend'; import { ScheduleBackgroundEventMethod } from './ScheduleBackgroundEventMethod'; export const handlers: Record = @@ -15,4 +16,5 @@ export const handlers: Record = onTransactionRejected, [ScheduleBackgroundEventMethod.OnSyncAccountTransactions]: onSyncAccountTransactions, + [ScheduleBackgroundEventMethod.RefreshSend]: refreshSend, }; diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx new file mode 100644 index 000000000..669dcbe83 --- /dev/null +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx @@ -0,0 +1,132 @@ +import type { OnCronjobHandler } from '@metamask/snaps-sdk'; + +import { DEFAULT_SEND_CONTEXT } from '../../../../features/send/render'; +import { Send } from '../../../../features/send/Send'; +import type { SendContext } from '../../../../features/send/types'; +import { assetsService, priceApiClient, state } from '../../../../snapContext'; +import type { UnencryptedStateValue } from '../../../services/state/State'; +import { + getInterfaceContextOrThrow, + getPreferences, + SEND_FORM_INTERFACE_NAME, + updateInterface, +} from '../../../utils/interface'; +import logger from '../../../utils/logger'; +import { ScheduleBackgroundEventMethod } from './ScheduleBackgroundEventMethod'; + +export const refreshSend: OnCronjobHandler = async () => { + try { + logger.info( + `[${ScheduleBackgroundEventMethod.RefreshSend}] Background event triggered`, + ); + + const [assets, mapInterfaceNameToId, preferences] = await Promise.all([ + assetsService.getAll(), + state.getKey( + 'mapInterfaceNameToId', + ), + getPreferences().catch(() => DEFAULT_SEND_CONTEXT.preferences), + ]); + + const assetTypes = assets.flatMap((asset) => asset.assetType); + + // let tokenPrices: SpotPrices = {}; + + // try { + // // First, fetch the token prices + // tokenPrices = await priceApiClient.getMultipleSpotPrices( + // assetTypes, + // preferences.currency, + // ); + + // // Then, update the state + // await state.setKey('tokenPrices', tokenPrices); + + // logger.info( + // `[${CronjobMethod.RefreshSend}] ✅ Token prices were properly refreshed and saved in the state.`, + // ); + // } catch (error) { + // logger.info( + // { error }, + // `[${CronjobMethod.RefreshSend}] ❌ Could not update the token prices in the state.`, + // ); + // } + + // try { + const sendFormInterfaceId = + mapInterfaceNameToId?.[SEND_FORM_INTERFACE_NAME]; + + // If the send form interface is not open, we don't need to refresh the token prices + if (!sendFormInterfaceId) { + logger.info( + `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ No send form interface found`, + ); + return; + } + + // If the interface is open, update the context + // if (sendFormInterfaceId) { + // First, fetch the token prices + const tokenPrices = await priceApiClient.getMultipleSpotPrices( + assetTypes, + preferences.currency, + ); + + // Get the current context + const interfaceContext = + await getInterfaceContextOrThrow(sendFormInterfaceId); + + // We only want to refresh the token prices when the user is in the transaction confirmation stage + if (interfaceContext.stage !== 'transaction-confirmation') { + logger.info( + `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ Not in transaction confirmation stage`, + ); + return; + } + + if (!interfaceContext.assets) { + logger.info( + `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ No assets found`, + ); + return; + } + + // Update the current context with the new rates + const updatedInterfaceContext = { + ...interfaceContext, + tokenPrices: { + ...interfaceContext.tokenPrices, + ...tokenPrices, + }, + }; + + await updateInterface( + sendFormInterfaceId, + , + updatedInterfaceContext, + ); + // } + // } catch (error) { + // logger.info( + // { error }, + // `[${CronjobMethod.RefreshSend}] ❌ Could not update the interface`, + // ); + // } + logger.info( + `[${ScheduleBackgroundEventMethod.RefreshSend}] ✅ Background event suceeded`, + ); + + // Schedule the next run (only if the send form interface is open) + if (sendFormInterfaceId) { + await snap.request({ + method: 'snap_scheduleBackgroundEvent', + params: { duration: 'PT30S', request: { method: 'refreshSend' } }, + }); + } + } catch (error) { + logger.warn( + { error }, + `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ Background event failed`, + ); + } +}; diff --git a/packages/snap/src/core/handlers/onCronjob/cronjobs/CronjobMethod.ts b/packages/snap/src/core/handlers/onCronjob/cronjobs/CronjobMethod.ts index 74151653d..17d0863bf 100644 --- a/packages/snap/src/core/handlers/onCronjob/cronjobs/CronjobMethod.ts +++ b/packages/snap/src/core/handlers/onCronjob/cronjobs/CronjobMethod.ts @@ -1,4 +1,3 @@ export enum CronjobMethod { - RefreshSend = 'refreshSend', RefreshConfirmationEstimation = 'refreshConfirmationEstimation', } diff --git a/packages/snap/src/core/handlers/onCronjob/cronjobs/index.ts b/packages/snap/src/core/handlers/onCronjob/cronjobs/index.ts index 5ebd6cf18..6359bef1a 100644 --- a/packages/snap/src/core/handlers/onCronjob/cronjobs/index.ts +++ b/packages/snap/src/core/handlers/onCronjob/cronjobs/index.ts @@ -2,9 +2,7 @@ import { type OnCronjobHandler } from '@metamask/snaps-sdk'; import { CronjobMethod } from './CronjobMethod'; import { refreshConfirmationEstimation } from './refreshConfirmationEstimation'; -import { refreshSend } from './refreshSend'; export const handlers: Record = { - [CronjobMethod.RefreshSend]: refreshSend, [CronjobMethod.RefreshConfirmationEstimation]: refreshConfirmationEstimation, }; diff --git a/packages/snap/src/core/handlers/onCronjob/cronjobs/refreshSend.tsx b/packages/snap/src/core/handlers/onCronjob/cronjobs/refreshSend.tsx deleted file mode 100644 index 3a02543e8..000000000 --- a/packages/snap/src/core/handlers/onCronjob/cronjobs/refreshSend.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import type { OnCronjobHandler } from '@metamask/snaps-sdk'; - -import { DEFAULT_SEND_CONTEXT } from '../../../../features/send/render'; -import { Send } from '../../../../features/send/Send'; -import type { SendContext } from '../../../../features/send/types'; -import { assetsService, priceApiClient, state } from '../../../../snapContext'; -import type { SpotPrices } from '../../../clients/price-api/types'; -import type { UnencryptedStateValue } from '../../../services/state/State'; -import { - getInterfaceContextOrThrow, - getPreferences, - SEND_FORM_INTERFACE_NAME, - updateInterface, -} from '../../../utils/interface'; -import logger from '../../../utils/logger'; -import { CronjobMethod } from './CronjobMethod'; - -export const refreshSend: OnCronjobHandler = async () => { - const [assets, mapInterfaceNameToId, preferences] = await Promise.all([ - assetsService.getAll(), - state.getKey( - 'mapInterfaceNameToId', - ), - getPreferences().catch(() => DEFAULT_SEND_CONTEXT.preferences), - ]); - - try { - logger.info(`[${CronjobMethod.RefreshSend}] Cronjob triggered`); - - const assetTypes = assets.flatMap((asset) => asset.assetType); - - let tokenPrices: SpotPrices = {}; - - try { - // First, fetch the token prices - tokenPrices = await priceApiClient.getMultipleSpotPrices( - assetTypes, - preferences.currency, - ); - - // Then, update the state - await state.setKey('tokenPrices', tokenPrices); - - logger.info( - `[${CronjobMethod.RefreshSend}] ✅ Token prices were properly refreshed and saved in the state.`, - ); - } catch (error) { - logger.info( - { error }, - `[${CronjobMethod.RefreshSend}] ❌ Could not update the token prices in the state.`, - ); - } - - try { - const sendFormInterfaceId = - mapInterfaceNameToId?.[SEND_FORM_INTERFACE_NAME]; - - // If the interface is open, update the context - if (sendFormInterfaceId) { - // Get the current context - const interfaceContext = - await getInterfaceContextOrThrow(sendFormInterfaceId); - - // we only want to refresh the token prices when the user is in the transaction confirmation stage - if (interfaceContext.stage !== 'transaction-confirmation') { - logger.info( - `[${CronjobMethod.RefreshSend}] ❌ Not in transaction confirmation stage`, - ); - return; - } - - if (!interfaceContext.assets) { - logger.info(`[${CronjobMethod.RefreshSend}] ❌ No assets found`); - return; - } - - // Update the current context with the new rates - const updatedInterfaceContext = { - ...interfaceContext, - tokenPrices: { - ...interfaceContext.tokenPrices, - ...tokenPrices, - }, - }; - - await updateInterface( - sendFormInterfaceId, - , - updatedInterfaceContext, - ); - } - } catch (error) { - logger.info( - { error }, - `[${CronjobMethod.RefreshSend}] ❌ Could not update the interface`, - ); - } - logger.info(`[${CronjobMethod.RefreshSend}] ✅ Cronjob suceeded`); - } catch (error) { - logger.info({ error }, `[${CronjobMethod.RefreshSend}] ❌ Cronjob failed`); - } -}; diff --git a/packages/snap/src/features/send/render.tsx b/packages/snap/src/features/send/render.tsx index 4e3dddad5..91ac12bfb 100644 --- a/packages/snap/src/features/send/render.tsx +++ b/packages/snap/src/features/send/render.tsx @@ -179,5 +179,11 @@ export const renderSend: OnRpcRequestHandler = async ({ request }) => { await state.setKey(`mapInterfaceNameToId.${SEND_FORM_INTERFACE_NAME}`, id); + // Schedule a refresh + await snap.request({ + method: 'snap_scheduleBackgroundEvent', + params: { duration: 'PT30S', request: { method: 'refreshSend' } }, + }); + return dialogPromise; }; From 3e40c5819501fea927334dc5bb8721e7cc65eebe Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 11:46:10 +0200 Subject: [PATCH 06/20] test: add unit test --- packages/snap/snap.manifest.json | 2 +- .../services/assets/AssetsService.test.ts | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index f1903fd0d..40ff0af31 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "KFKDWwTS+AdnzysJ0rV7mBuR2o81k6KHehDJlhmxWxE=", + "shasum": "IZGPE19hNMJz3zslo/aLiq9BNw4vO7yze/A/L2yOnOQ=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/snap/src/core/services/assets/AssetsService.test.ts b/packages/snap/src/core/services/assets/AssetsService.test.ts index 7821afbfa..6c88c1293 100644 --- a/packages/snap/src/core/services/assets/AssetsService.test.ts +++ b/packages/snap/src/core/services/assets/AssetsService.test.ts @@ -429,6 +429,42 @@ describe('AssetsService', () => { }, ); }); + + it('does not include native assets in removed array even when they have zero balance', async () => { + const nativeAssetWithZeroBalance = { + ...MOCK_ASSET_ENTITY_0, + rawAmount: '0', + }; + const tokenAssetWithZeroBalance = { + ...MOCK_ASSET_ENTITY_1, + rawAmount: '0', + }; + + // Mock that both assets existed with non-zero balance + jest.spyOn(mockAssetsRepository, 'getAll').mockResolvedValueOnce([ + MOCK_ASSET_ENTITY_0, // Native asset with non-zero balance + MOCK_ASSET_ENTITY_1, // Token asset with non-zero balance + ]); + + await assetsService.saveMany([ + nativeAssetWithZeroBalance, + tokenAssetWithZeroBalance, + ]); + + // Should emit AccountAssetListUpdated with only the token asset in the removed array + expect(emitSnapKeyringEvent).toHaveBeenCalledWith( + snap, + KeyringEvent.AccountAssetListUpdated, + { + assets: { + [MOCK_SOLANA_KEYRING_ACCOUNT_0.id]: { + added: [], + removed: [MOCK_ASSET_ENTITY_1.assetType], // Only token asset, not native + }, + }, + }, + ); + }); }); describe('hasChanged', () => { From 77ef1e064725727e9cc9466bf790cfa5c10c4195 Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 13:29:40 +0200 Subject: [PATCH 07/20] feat: only run refresh cronjobs when the matching UI is open --- package.json | 2 +- packages/snap/snap.manifest.json | 14 ++---- .../ScheduleBackgroundEventMethod.ts | 2 + .../onCronjob/backgroundEvents/index.ts | 3 ++ .../refreshConfirmationEstimation.tsx | 27 ++++++---- .../backgroundEvents/refreshSend.tsx | 49 +++---------------- .../onCronjob/cronjobs/CronjobMethod.ts | 4 +- .../core/handlers/onCronjob/cronjobs/index.ts | 7 +-- .../ConfirmTransactionRequest/render.tsx | 9 ++++ packages/snap/src/features/send/render.tsx | 2 +- yarn.lock | 8 +-- 11 files changed, 52 insertions(+), 75 deletions(-) rename packages/snap/src/core/handlers/onCronjob/{cronjobs => backgroundEvents}/refreshConfirmationEstimation.tsx (76%) diff --git a/package.json b/package.json index b9647ef9e..d8c5669f1 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ ] }, "resolutions": { - "@metamask/snaps-sdk": "9.3.0" + "@metamask/snaps-sdk": "9.2.0" }, "devDependencies": { "@commitlint/cli": "^17.7.1", diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index 40ff0af31..a8b99fb32 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "IZGPE19hNMJz3zslo/aLiq9BNw4vO7yze/A/L2yOnOQ=", + "shasum": "Otjpsywv3pAMCEeVfeEUkFRxD6WkmSli3CEVrdHlXGE=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -42,15 +42,7 @@ "endowment:lifecycle-hooks": {}, "endowment:network-access": {}, "endowment:cronjob": { - "jobs": [ - { - "duration": "PT20S", - "request": { - "method": "refreshConfirmationEstimation", - "params": {} - } - } - ] + "jobs": [] }, "endowment:protocol": { "scopes": { @@ -87,6 +79,6 @@ "snap_dialog": {}, "snap_getPreferences": {} }, - "platformVersion": "9.3.0", + "platformVersion": "9.2.0", "manifestVersion": "0.1" } diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/ScheduleBackgroundEventMethod.ts b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/ScheduleBackgroundEventMethod.ts index 82747a0e5..d6e3d1d86 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/ScheduleBackgroundEventMethod.ts +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/ScheduleBackgroundEventMethod.ts @@ -9,4 +9,6 @@ export enum ScheduleBackgroundEventMethod { OnSyncAccountTransactions = 'onSyncAccountTransactions', /** Use it to schedule a background event to refresh the send form */ RefreshSend = 'refreshSend', + /** Use it to schedule a background event to refresh the confirmation estimation */ + RefreshConfirmationEstimation = 'refreshConfirmationEstimation', } diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/index.ts b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/index.ts index 25afa4047..63ecab95d 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/index.ts +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/index.ts @@ -4,6 +4,7 @@ import { onSyncAccountTransactions } from './onSyncAccountTransactions'; import { onTransactionAdded } from './onTransactionAdded'; import { onTransactionApproved } from './onTransactionApproved'; import { onTransactionRejected } from './onTransactionRejected'; +import { refreshConfirmationEstimation } from './refreshConfirmationEstimation'; import { refreshSend } from './refreshSend'; import { ScheduleBackgroundEventMethod } from './ScheduleBackgroundEventMethod'; @@ -17,4 +18,6 @@ export const handlers: Record = [ScheduleBackgroundEventMethod.OnSyncAccountTransactions]: onSyncAccountTransactions, [ScheduleBackgroundEventMethod.RefreshSend]: refreshSend, + [ScheduleBackgroundEventMethod.RefreshConfirmationEstimation]: + refreshConfirmationEstimation, }; diff --git a/packages/snap/src/core/handlers/onCronjob/cronjobs/refreshConfirmationEstimation.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx similarity index 76% rename from packages/snap/src/core/handlers/onCronjob/cronjobs/refreshConfirmationEstimation.tsx rename to packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx index dc394a37d..ce37475ac 100644 --- a/packages/snap/src/core/handlers/onCronjob/cronjobs/refreshConfirmationEstimation.tsx +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx @@ -10,12 +10,12 @@ import { updateInterface, } from '../../../utils/interface'; import logger from '../../../utils/logger'; -import { CronjobMethod } from './CronjobMethod'; +import { ScheduleBackgroundEventMethod } from './ScheduleBackgroundEventMethod'; export const refreshConfirmationEstimation: OnCronjobHandler = async () => { try { logger.info( - `[${CronjobMethod.RefreshConfirmationEstimation}] Cronjob triggered`, + `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event triggered`, ); const mapInterfaceNameToId = @@ -29,6 +29,15 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { // Update the interface context with the new rates. try { if (confirmationInterfaceId) { + // Schedule the next run + await snap.request({ + method: 'snap_scheduleBackgroundEvent', + params: { + duration: 'PT20S', + request: { method: 'refreshConfirmationEstimation' }, + }, + }); + // Get the current context const interfaceContext = await getInterfaceContextOrThrow( @@ -42,7 +51,7 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { !interfaceContext.method ) { logger.info( - `[${CronjobMethod.RefreshConfirmationEstimation}] Context is missing required fields`, + `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Context is missing required fields`, ); return; } @@ -50,7 +59,7 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { // Skip transaction simulation if the preference is disabled if (!interfaceContext.preferences?.simulateOnChainActions) { logger.info( - `[${CronjobMethod.RefreshConfirmationEstimation}] Transaction simulation is disabled in preferences`, + `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Transaction simulation is disabled in preferences`, ); return; } @@ -88,7 +97,7 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { }; logger.info( - `[${CronjobMethod.RefreshConfirmationEstimation}] New scan fetched`, + `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] New scan fetched`, ); await updateInterface( @@ -99,12 +108,12 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { } logger.info( - `[${CronjobMethod.RefreshConfirmationEstimation}] Cronjob suceeded`, + `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event suceeded`, ); } catch (error) { if (!confirmationInterfaceId) { logger.info( - `[${CronjobMethod.RefreshConfirmationEstimation}] No interface context found`, + `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] No interface context found`, ); return; } @@ -127,13 +136,13 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { logger.info( { error }, - `[${CronjobMethod.RefreshConfirmationEstimation}] Could not update the interface. But rolled back status to fetched.`, + `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Could not update the interface. But rolled back status to fetched.`, ); } } catch (error) { logger.info( { error }, - `[${CronjobMethod.RefreshConfirmationEstimation}] Cronjob failed`, + `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event failed`, ); } }; diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx index 669dcbe83..baa918a70 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx @@ -30,33 +30,10 @@ export const refreshSend: OnCronjobHandler = async () => { const assetTypes = assets.flatMap((asset) => asset.assetType); - // let tokenPrices: SpotPrices = {}; - - // try { - // // First, fetch the token prices - // tokenPrices = await priceApiClient.getMultipleSpotPrices( - // assetTypes, - // preferences.currency, - // ); - - // // Then, update the state - // await state.setKey('tokenPrices', tokenPrices); - - // logger.info( - // `[${CronjobMethod.RefreshSend}] ✅ Token prices were properly refreshed and saved in the state.`, - // ); - // } catch (error) { - // logger.info( - // { error }, - // `[${CronjobMethod.RefreshSend}] ❌ Could not update the token prices in the state.`, - // ); - // } - - // try { const sendFormInterfaceId = mapInterfaceNameToId?.[SEND_FORM_INTERFACE_NAME]; - // If the send form interface is not open, we don't need to refresh the token prices + // Don't do anything if the send form interface is not open if (!sendFormInterfaceId) { logger.info( `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ No send form interface found`, @@ -64,8 +41,12 @@ export const refreshSend: OnCronjobHandler = async () => { return; } - // If the interface is open, update the context - // if (sendFormInterfaceId) { + // Schedule the next run + await snap.request({ + method: 'snap_scheduleBackgroundEvent', + params: { duration: 'PT30S', request: { method: 'refreshSend' } }, + }); + // First, fetch the token prices const tokenPrices = await priceApiClient.getMultipleSpotPrices( assetTypes, @@ -105,24 +86,10 @@ export const refreshSend: OnCronjobHandler = async () => { , updatedInterfaceContext, ); - // } - // } catch (error) { - // logger.info( - // { error }, - // `[${CronjobMethod.RefreshSend}] ❌ Could not update the interface`, - // ); - // } + logger.info( `[${ScheduleBackgroundEventMethod.RefreshSend}] ✅ Background event suceeded`, ); - - // Schedule the next run (only if the send form interface is open) - if (sendFormInterfaceId) { - await snap.request({ - method: 'snap_scheduleBackgroundEvent', - params: { duration: 'PT30S', request: { method: 'refreshSend' } }, - }); - } } catch (error) { logger.warn( { error }, diff --git a/packages/snap/src/core/handlers/onCronjob/cronjobs/CronjobMethod.ts b/packages/snap/src/core/handlers/onCronjob/cronjobs/CronjobMethod.ts index 17d0863bf..ccaa21221 100644 --- a/packages/snap/src/core/handlers/onCronjob/cronjobs/CronjobMethod.ts +++ b/packages/snap/src/core/handlers/onCronjob/cronjobs/CronjobMethod.ts @@ -1,3 +1 @@ -export enum CronjobMethod { - RefreshConfirmationEstimation = 'refreshConfirmationEstimation', -} +export enum CronjobMethod {} diff --git a/packages/snap/src/core/handlers/onCronjob/cronjobs/index.ts b/packages/snap/src/core/handlers/onCronjob/cronjobs/index.ts index 6359bef1a..5de4cd4d2 100644 --- a/packages/snap/src/core/handlers/onCronjob/cronjobs/index.ts +++ b/packages/snap/src/core/handlers/onCronjob/cronjobs/index.ts @@ -1,8 +1,5 @@ import { type OnCronjobHandler } from '@metamask/snaps-sdk'; -import { CronjobMethod } from './CronjobMethod'; -import { refreshConfirmationEstimation } from './refreshConfirmationEstimation'; +import type { CronjobMethod } from './CronjobMethod'; -export const handlers: Record = { - [CronjobMethod.RefreshConfirmationEstimation]: refreshConfirmationEstimation, -}; +export const handlers: Record = {}; diff --git a/packages/snap/src/features/confirmation/views/ConfirmTransactionRequest/render.tsx b/packages/snap/src/features/confirmation/views/ConfirmTransactionRequest/render.tsx index 8fda104c3..82189c3c1 100644 --- a/packages/snap/src/features/confirmation/views/ConfirmTransactionRequest/render.tsx +++ b/packages/snap/src/features/confirmation/views/ConfirmTransactionRequest/render.tsx @@ -209,5 +209,14 @@ export async function render( id, ); + // Schedule the next refresh + await snap.request({ + method: 'snap_scheduleBackgroundEvent', + params: { + duration: 'PT20S', + request: { method: 'refreshConfirmationEstimation' }, + }, + }); + return dialogPromise; } diff --git a/packages/snap/src/features/send/render.tsx b/packages/snap/src/features/send/render.tsx index 91ac12bfb..477ae7bc9 100644 --- a/packages/snap/src/features/send/render.tsx +++ b/packages/snap/src/features/send/render.tsx @@ -179,7 +179,7 @@ export const renderSend: OnRpcRequestHandler = async ({ request }) => { await state.setKey(`mapInterfaceNameToId.${SEND_FORM_INTERFACE_NAME}`, id); - // Schedule a refresh + // Schedule the next refresh await snap.request({ method: 'snap_scheduleBackgroundEvent', params: { duration: 'PT30S', request: { method: 'refreshSend' } }, diff --git a/yarn.lock b/yarn.lock index f261e4666..4f8db23be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4583,16 +4583,16 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-sdk@npm:9.3.0": - version: 9.3.0 - resolution: "@metamask/snaps-sdk@npm:9.3.0" +"@metamask/snaps-sdk@npm:9.2.0": + version: 9.2.0 + resolution: "@metamask/snaps-sdk@npm:9.2.0" dependencies: "@metamask/key-tree": ^10.1.1 "@metamask/providers": ^22.1.0 "@metamask/rpc-errors": ^7.0.3 "@metamask/superstruct": ^3.2.1 "@metamask/utils": ^11.4.2 - checksum: 0ddffc266c3802ab97724af94d07356ac8e55d91030d3f246e5f2c9154c61e8eae6f0b320cafd9bd8eca245d83c5603d0aae8c7426ed12496d512728970f4153 + checksum: 29b1c06b4d9874928b4588dcea5141090695246f5bea70b7d67d4b630ade6ba2ac7e3adc3ee52569115970621be74545536a653dafedd37021014306d9a61ce7 languageName: node linkType: hard From 26870c4967b7213f35d7094d01cbaebe3d27d1eb Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 13:34:06 +0200 Subject: [PATCH 08/20] chore: shasum --- packages/snap/snap.manifest.json | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index a8b99fb32..d61ad4cb7 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "Otjpsywv3pAMCEeVfeEUkFRxD6WkmSli3CEVrdHlXGE=", + "shasum": "/YPf9jrUwm/lzjQ9Q5jv0wmwQ/ao8LlyMG9WaKGgZZc=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -19,8 +19,7 @@ "locales": ["locales/en.json"] }, "initialConnections": { - "https://portfolio.metamask.io": {}, - "http://localhost:3000": {} + "https://portfolio.metamask.io": {} }, "initialPermissions": { "endowment:rpc": { @@ -28,10 +27,7 @@ "snaps": false }, "endowment:keyring": { - "allowedOrigins": [ - "https://portfolio.metamask.io", - "http://localhost:3000" - ] + "allowedOrigins": ["https://portfolio.metamask.io"] }, "snap_getBip32Entropy": [ { From 8135396e47da58a6e3950cd78e5118c921dafd71 Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 13:36:23 +0200 Subject: [PATCH 09/20] chore: improve logging --- packages/snap/snap.manifest.json | 2 +- .../backgroundEvents/refreshConfirmationEstimation.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index d61ad4cb7..45eda43d7 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "/YPf9jrUwm/lzjQ9Q5jv0wmwQ/ao8LlyMG9WaKGgZZc=", + "shasum": "8tsQZGNmZP6fxyU/YuUeIWMKUlsM9DaLVFc5GclKjVA=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx index ce37475ac..55520c2f7 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx @@ -140,7 +140,7 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { ); } } catch (error) { - logger.info( + logger.warn( { error }, `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event failed`, ); From c6a93c67d91a854daa50babff62d382f9b638383 Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 14:07:15 +0200 Subject: [PATCH 10/20] chore: clean up --- package.json | 2 +- packages/snap/package.json | 4 +-- packages/snap/snap.manifest.json | 4 +-- .../refreshConfirmationEstimation.tsx | 7 ++++- .../backgroundEvents/refreshSend.tsx | 7 ++++- packages/snap/src/index.ts | 23 ++++++++++---- yarn.lock | 30 +++++++++---------- 7 files changed, 50 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index d8c5669f1..b9647ef9e 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ ] }, "resolutions": { - "@metamask/snaps-sdk": "9.2.0" + "@metamask/snaps-sdk": "9.3.0" }, "devDependencies": { "@commitlint/cli": "^17.7.1", diff --git a/packages/snap/package.json b/packages/snap/package.json index bb4955e48..85b59c42f 100644 --- a/packages/snap/package.json +++ b/packages/snap/package.json @@ -44,8 +44,8 @@ "@metamask/keyring-api": "^18.0.0", "@metamask/keyring-snap-sdk": "^4.0.0", "@metamask/snaps-cli": "^8.1.1", - "@metamask/snaps-jest": "9.4.0", - "@metamask/snaps-sdk": "^9.2.0", + "@metamask/snaps-jest": "9.3.0", + "@metamask/snaps-sdk": "^9.3.0", "@metamask/superstruct": "^3.1.0", "@metamask/utils": "11.4.0", "@noble/ed25519": "2.1.0", diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index 45eda43d7..8ffd17e79 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "8tsQZGNmZP6fxyU/YuUeIWMKUlsM9DaLVFc5GclKjVA=", + "shasum": "nbmoogrvVXyf2R4G900YK4igZ/e8YEGojIOuTweG1VI=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -75,6 +75,6 @@ "snap_dialog": {}, "snap_getPreferences": {} }, - "platformVersion": "9.2.0", + "platformVersion": "9.3.0", "manifestVersion": "0.1" } diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx index 55520c2f7..4d7ec24d2 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx @@ -9,10 +9,15 @@ import { getInterfaceContextOrThrow, updateInterface, } from '../../../utils/interface'; -import logger from '../../../utils/logger'; +import baseLogger, { createPrefixedLogger } from '../../../utils/logger'; import { ScheduleBackgroundEventMethod } from './ScheduleBackgroundEventMethod'; export const refreshConfirmationEstimation: OnCronjobHandler = async () => { + const logger = createPrefixedLogger( + baseLogger, + '[refreshConfirmationEstimation]', + ); + try { logger.info( `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event triggered`, diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx index baa918a70..c31479133 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx @@ -11,10 +11,12 @@ import { SEND_FORM_INTERFACE_NAME, updateInterface, } from '../../../utils/interface'; -import logger from '../../../utils/logger'; +import baseLogger, { createPrefixedLogger } from '../../../utils/logger'; import { ScheduleBackgroundEventMethod } from './ScheduleBackgroundEventMethod'; export const refreshSend: OnCronjobHandler = async () => { + const logger = createPrefixedLogger(baseLogger, '[refreshSend]'); + try { logger.info( `[${ScheduleBackgroundEventMethod.RefreshSend}] Background event triggered`, @@ -53,6 +55,9 @@ export const refreshSend: OnCronjobHandler = async () => { preferences.currency, ); + // Save them in the state + await state.setKey('tokenPrices', tokenPrices); + // Get the current context const interfaceContext = await getInterfaceContextOrThrow(sendFormInterfaceId); diff --git a/packages/snap/src/index.ts b/packages/snap/src/index.ts index 0f8bd135d..a8c5cf90b 100644 --- a/packages/snap/src/index.ts +++ b/packages/snap/src/index.ts @@ -39,7 +39,7 @@ import { handlers as onRpcRequestHandlers } from './core/handlers/onRpcRequest'; import { RpcRequestMethod } from './core/handlers/onRpcRequest/types'; import { withCatchAndThrowSnapError } from './core/utils/errors'; import { getClientStatus } from './core/utils/interface'; -import logger from './core/utils/logger'; +import logger, { createPrefixedLogger } from './core/utils/logger'; import { validateOrigin } from './core/validation/validators'; import { eventHandlers as confirmSignInEvents } from './features/confirmation/views/ConfirmSignIn/events'; import { eventHandlers as confirmSignMessageEvents } from './features/confirmation/views/ConfirmSignMessage/events'; @@ -181,7 +181,9 @@ export const onUserInput: OnUserInputHandler = async ({ * @see https://docs.metamask.io/snaps/reference/entry-points/#oncronjob */ export const onCronjob: OnCronjobHandler = async ({ request }) => { - logger.log('[⏱️ onCronjob]', request.method, request); + const _logger = createPrefixedLogger(logger, '[⏱️ onCronjob]'); + + _logger.log(request.method, request); const { method } = request; assert( @@ -200,15 +202,26 @@ export const onCronjob: OnCronjobHandler = async ({ request }) => { */ const { locked, active } = await getClientStatus(); - logger.log('[🔑 onCronjob] Client status', { locked, active }); + _logger.log('Client status', { locked, active }); if (locked || !active) { return Promise.resolve(); } - logger.log('[🔑 onCronjob] Running cronjob', { method }); + _logger.log('Running cronjob', { method }); + + const handler = + onCronjobHandlers[ + method as CronjobMethod | ScheduleBackgroundEventMethod + ]; - const handler = onCronjobHandlers[method]; + if (!handler) { + throw new MethodNotFoundError( + `Cronjob method ${method} not found. Available methods: ${Object.values( + CronjobMethod, + ).toString()}`, + ) as unknown as Error; + } return handler({ request }); }); diff --git a/yarn.lock b/yarn.lock index 4f8db23be..0c9f33426 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4437,7 +4437,7 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-controllers@npm:^14.2.0": +"@metamask/snaps-controllers@npm:^14.1.0, @metamask/snaps-controllers@npm:^14.2.0": version: 14.2.0 resolution: "@metamask/snaps-controllers@npm:14.2.0" dependencies: @@ -4496,23 +4496,23 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-jest@npm:9.4.0": - version: 9.4.0 - resolution: "@metamask/snaps-jest@npm:9.4.0" +"@metamask/snaps-jest@npm:9.3.0": + version: 9.3.0 + resolution: "@metamask/snaps-jest@npm:9.3.0" dependencies: "@jest/environment": ^29.5.0 "@jest/expect": ^29.5.0 "@jest/globals": ^29.5.0 - "@metamask/snaps-controllers": ^14.2.0 - "@metamask/snaps-sdk": ^9.3.0 - "@metamask/snaps-simulation": ^3.4.0 + "@metamask/snaps-controllers": ^14.1.0 + "@metamask/snaps-sdk": ^9.2.0 + "@metamask/snaps-simulation": ^3.3.0 "@metamask/superstruct": ^3.2.1 "@metamask/utils": ^11.4.2 express: ^5.1.0 jest-environment-node: ^29.5.0 jest-matcher-utils: ^29.5.0 redux: ^4.2.1 - checksum: 4f0be1a02eadad5a5917fec98335dd9773358213f43f0d96dfbcc9c815bc8bab75cf65f4712dc8260bd85e55edc16fcb3990e3242e8e9a9ae3c9a2f573416ecc + checksum: de519a8da49c4cb7def5e0674949e7759d0788001ee7611f8fbb1024c619ff10f3463122645ccad236bb9ddca6e4e332e8039658a5a5b7f3611efe0435434802 languageName: node linkType: hard @@ -4583,20 +4583,20 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-sdk@npm:9.2.0": - version: 9.2.0 - resolution: "@metamask/snaps-sdk@npm:9.2.0" +"@metamask/snaps-sdk@npm:9.3.0": + version: 9.3.0 + resolution: "@metamask/snaps-sdk@npm:9.3.0" dependencies: "@metamask/key-tree": ^10.1.1 "@metamask/providers": ^22.1.0 "@metamask/rpc-errors": ^7.0.3 "@metamask/superstruct": ^3.2.1 "@metamask/utils": ^11.4.2 - checksum: 29b1c06b4d9874928b4588dcea5141090695246f5bea70b7d67d4b630ade6ba2ac7e3adc3ee52569115970621be74545536a653dafedd37021014306d9a61ce7 + checksum: 0ddffc266c3802ab97724af94d07356ac8e55d91030d3f246e5f2c9154c61e8eae6f0b320cafd9bd8eca245d83c5603d0aae8c7426ed12496d512728970f4153 languageName: node linkType: hard -"@metamask/snaps-simulation@npm:^3.4.0": +"@metamask/snaps-simulation@npm:^3.3.0": version: 3.4.0 resolution: "@metamask/snaps-simulation@npm:3.4.0" dependencies: @@ -4744,8 +4744,8 @@ __metadata: "@metamask/keyring-api": ^18.0.0 "@metamask/keyring-snap-sdk": ^4.0.0 "@metamask/snaps-cli": ^8.1.1 - "@metamask/snaps-jest": 9.4.0 - "@metamask/snaps-sdk": ^9.2.0 + "@metamask/snaps-jest": 9.3.0 + "@metamask/snaps-sdk": ^9.3.0 "@metamask/superstruct": ^3.1.0 "@metamask/utils": 11.4.0 "@noble/ed25519": 2.1.0 From b4c89ba41bd62e6fc7743f12231cd1be887f7976 Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 14:12:26 +0200 Subject: [PATCH 11/20] fix: review remarks --- packages/snap/snap.manifest.json | 2 +- .../refreshConfirmationEstimation.tsx | 18 +++++++++--------- packages/snap/src/index.ts | 7 +++++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index 8ffd17e79..96511f924 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "nbmoogrvVXyf2R4G900YK4igZ/e8YEGojIOuTweG1VI=", + "shasum": "Zwhy5eSOo7J6gHkkyoBRuTB+H/bDjEVRi8SHpTXsVbM=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx index 4d7ec24d2..64dbfdc0f 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx @@ -34,15 +34,6 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { // Update the interface context with the new rates. try { if (confirmationInterfaceId) { - // Schedule the next run - await snap.request({ - method: 'snap_scheduleBackgroundEvent', - params: { - duration: 'PT20S', - request: { method: 'refreshConfirmationEstimation' }, - }, - }); - // Get the current context const interfaceContext = await getInterfaceContextOrThrow( @@ -115,6 +106,15 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { logger.info( `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event suceeded`, ); + + // Schedule the next run + await snap.request({ + method: 'snap_scheduleBackgroundEvent', + params: { + duration: 'PT20S', + request: { method: 'refreshConfirmationEstimation' }, + }, + }); } catch (error) { if (!confirmationInterfaceId) { logger.info( diff --git a/packages/snap/src/index.ts b/packages/snap/src/index.ts index a8c5cf90b..8b53c13a4 100644 --- a/packages/snap/src/index.ts +++ b/packages/snap/src/index.ts @@ -217,8 +217,11 @@ export const onCronjob: OnCronjobHandler = async ({ request }) => { if (!handler) { throw new MethodNotFoundError( - `Cronjob method ${method} not found. Available methods: ${Object.values( - CronjobMethod, + `Cronjob / ScheduleBackgroundEvent method ${method} not found. Available methods: ${Object.values( + [ + ...Object.values(CronjobMethod), + ...Object.values(ScheduleBackgroundEventMethod), + ], ).toString()}`, ) as unknown as Error; } From f973013887ad04dd98ecc40ed64f54539c66e17d Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 14:15:45 +0200 Subject: [PATCH 12/20] docs: jsdoc --- packages/snap/src/index.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/snap/src/index.ts b/packages/snap/src/index.ts index 8b53c13a4..c1d3b207d 100644 --- a/packages/snap/src/index.ts +++ b/packages/snap/src/index.ts @@ -195,11 +195,7 @@ export const onCronjob: OnCronjobHandler = async ({ request }) => { ); const result = await withCatchAndThrowSnapError(async () => { - /** - * Don't run cronjobs if client is locked or inactive - * - We don't want to call cronjobs if the client is locked - * - We don't want to call cronjobs if the client is inactive - */ + // Don't run cronjobs if client is locked or inactive const { locked, active } = await getClientStatus(); _logger.log('Client status', { locked, active }); From e719e08aa12c75f7c9f1bc0b24b1f8cb22375971 Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 14:20:55 +0200 Subject: [PATCH 13/20] test: fix unit test --- packages/snap/snap.manifest.json | 10 +++++++--- packages/snap/src/index.test.ts | 18 +++++++++--------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index 96511f924..d27d2833b 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "Zwhy5eSOo7J6gHkkyoBRuTB+H/bDjEVRi8SHpTXsVbM=", + "shasum": "OPLP+HsUF6frgznB7/NdCebBGeuXqzpQeC4qWPTxA50=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -19,7 +19,8 @@ "locales": ["locales/en.json"] }, "initialConnections": { - "https://portfolio.metamask.io": {} + "https://portfolio.metamask.io": {}, + "http://localhost:3000": {} }, "initialPermissions": { "endowment:rpc": { @@ -27,7 +28,10 @@ "snaps": false }, "endowment:keyring": { - "allowedOrigins": ["https://portfolio.metamask.io"] + "allowedOrigins": [ + "https://portfolio.metamask.io", + "http://localhost:3000" + ] }, "snap_getBip32Entropy": [ { diff --git a/packages/snap/src/index.test.ts b/packages/snap/src/index.test.ts index 2713de8f5..67fe8983a 100644 --- a/packages/snap/src/index.test.ts +++ b/packages/snap/src/index.test.ts @@ -3,7 +3,7 @@ import { installSnap } from '@metamask/snaps-jest'; import { onCronjob } from '.'; import { handlers } from './core/handlers/onCronjob'; -import { CronjobMethod } from './core/handlers/onCronjob/cronjobs/CronjobMethod'; +import { ScheduleBackgroundEventMethod } from './core/handlers/onCronjob/backgroundEvents/ScheduleBackgroundEventMethod'; jest.mock('@noble/ed25519', () => ({ getPublicKey: jest.fn(), @@ -75,7 +75,7 @@ describe('onCronjob', () => { it('calls the correct handler when the method is valid and snap is not locked', async () => { const handler = jest.fn(); - handlers[CronjobMethod.RefreshSend] = handler; + handlers[ScheduleBackgroundEventMethod.RefreshSend] = handler; const snap = { request: jest.fn().mockResolvedValue({ locked: false, active: true }), @@ -87,7 +87,7 @@ describe('onCronjob', () => { request: { id: '1', jsonrpc: '2.0', - method: CronjobMethod.RefreshSend, + method: ScheduleBackgroundEventMethod.RefreshSend, }, }); @@ -96,7 +96,7 @@ describe('onCronjob', () => { it('does not call the handler when the client is locked', async () => { const handler = jest.fn(); - handlers[CronjobMethod.RefreshSend] = handler; + handlers[ScheduleBackgroundEventMethod.RefreshSend] = handler; const snap = { request: jest.fn().mockResolvedValue({ locked: true, active: true }), @@ -108,7 +108,7 @@ describe('onCronjob', () => { request: { id: '1', jsonrpc: '2.0', - method: CronjobMethod.RefreshSend, + method: ScheduleBackgroundEventMethod.RefreshSend, }, }); @@ -117,7 +117,7 @@ describe('onCronjob', () => { it('does not call the handler when the client is inactive', async () => { const handler = jest.fn(); - handlers[CronjobMethod.RefreshSend] = handler; + handlers[ScheduleBackgroundEventMethod.RefreshSend] = handler; const snap = { request: jest.fn().mockResolvedValue({ active: false, locked: false }), @@ -129,7 +129,7 @@ describe('onCronjob', () => { request: { id: '1', jsonrpc: '2.0', - method: CronjobMethod.RefreshSend, + method: ScheduleBackgroundEventMethod.RefreshSend, }, }); @@ -138,7 +138,7 @@ describe('onCronjob', () => { it('does call the handler when the client is unlocked and active', async () => { const handler = jest.fn(); - handlers[CronjobMethod.RefreshSend] = handler; + handlers[ScheduleBackgroundEventMethod.RefreshSend] = handler; const snap = { request: jest.fn().mockResolvedValue({ locked: false, active: true }), @@ -150,7 +150,7 @@ describe('onCronjob', () => { request: { id: '1', jsonrpc: '2.0', - method: CronjobMethod.RefreshSend, + method: ScheduleBackgroundEventMethod.RefreshSend, }, }); From 3ab27bc2795cee28f8d742d1a58c7e25678bf293 Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 14:31:00 +0200 Subject: [PATCH 14/20] fix: scheduling flow --- packages/snap/snap.manifest.json | 2 +- .../refreshConfirmationEstimation.tsx | 233 +++++++++--------- 2 files changed, 117 insertions(+), 118 deletions(-) diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index d27d2833b..7f9ed7ac9 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "OPLP+HsUF6frgznB7/NdCebBGeuXqzpQeC4qWPTxA50=", + "shasum": "aqOdyICdZWwwirkDtw8MwwMHNiqT/nJblgJCwYCyppw=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx index 64dbfdc0f..ae8ac7f4b 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx @@ -18,136 +18,135 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { '[refreshConfirmationEstimation]', ); - try { + // try { + logger.info( + `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event triggered`, + ); + + const mapInterfaceNameToId = + (await state.getKey( + 'mapInterfaceNameToId', + )) ?? {}; + + const confirmationInterfaceId = + mapInterfaceNameToId[CONFIRM_SIGN_AND_SEND_TRANSACTION_INTERFACE_NAME]; + + // Don't do anything if the confirmation interface is not open + if (!confirmationInterfaceId) { logger.info( - `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event triggered`, + `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] No interface context found`, ); + return; + } - const mapInterfaceNameToId = - (await state.getKey( - 'mapInterfaceNameToId', - )) ?? {}; - - const confirmationInterfaceId = - mapInterfaceNameToId[CONFIRM_SIGN_AND_SEND_TRANSACTION_INTERFACE_NAME]; - - // Update the interface context with the new rates. - try { - if (confirmationInterfaceId) { - // Get the current context - const interfaceContext = - await getInterfaceContextOrThrow( - confirmationInterfaceId, - ); - - if ( - !interfaceContext.account?.address || - !interfaceContext.transaction || - !interfaceContext.scope || - !interfaceContext.method - ) { - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Context is missing required fields`, - ); - return; - } - - // Skip transaction simulation if the preference is disabled - if (!interfaceContext.preferences?.simulateOnChainActions) { - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Transaction simulation is disabled in preferences`, - ); - return; - } - - const fetchingConfirmationContext = { - ...interfaceContext, - scanFetchStatus: 'fetching', - } as ConfirmTransactionRequestContext; - - await updateInterface( - confirmationInterfaceId, - , - fetchingConfirmationContext, - ); - - const scan = await transactionScanService.scanTransaction({ - method: interfaceContext.method, - accountAddress: interfaceContext.account.address, - transaction: interfaceContext.transaction, - scope: interfaceContext.scope, - origin: interfaceContext.origin, - account: interfaceContext.account, - }); - - const updatedInterfaceContextFinal = - await getInterfaceContextOrThrow( - confirmationInterfaceId, - ); - - // Update the current context with the new rates - const updatedInterfaceContext = { - ...updatedInterfaceContextFinal, - scanFetchStatus: 'fetched' as const, - scan, - }; - - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] New scan fetched`, - ); - - await updateInterface( - confirmationInterfaceId, - , - updatedInterfaceContext, - ); - } + // Schedule the next run + await snap.request({ + method: 'snap_scheduleBackgroundEvent', + params: { + duration: 'PT20S', + request: { method: 'refreshConfirmationEstimation' }, + }, + }); - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event suceeded`, + // Update the interface context with the new rates. + try { + // Get the current context + const interfaceContext = + await getInterfaceContextOrThrow( + confirmationInterfaceId, ); - // Schedule the next run - await snap.request({ - method: 'snap_scheduleBackgroundEvent', - params: { - duration: 'PT20S', - request: { method: 'refreshConfirmationEstimation' }, - }, - }); - } catch (error) { - if (!confirmationInterfaceId) { - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] No interface context found`, - ); - return; - } - - const fetchedInterfaceContext = - await getInterfaceContextOrThrow( - confirmationInterfaceId, - ); - - const fetchingConfirmationContext = { - ...fetchedInterfaceContext, - scanFetchStatus: 'fetched', - } as ConfirmTransactionRequestContext; - - await updateInterface( - confirmationInterfaceId, - , - fetchingConfirmationContext, + if ( + !interfaceContext.account?.address || + !interfaceContext.transaction || + !interfaceContext.scope || + !interfaceContext.method + ) { + logger.info( + `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Context is missing required fields`, ); + return; + } + // Skip transaction simulation if the preference is disabled + if (!interfaceContext.preferences?.simulateOnChainActions) { logger.info( - { error }, - `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Could not update the interface. But rolled back status to fetched.`, + `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Transaction simulation is disabled in preferences`, ); + return; } + + const fetchingConfirmationContext = { + ...interfaceContext, + scanFetchStatus: 'fetching', + } as ConfirmTransactionRequestContext; + + await updateInterface( + confirmationInterfaceId, + , + fetchingConfirmationContext, + ); + + const scan = await transactionScanService.scanTransaction({ + method: interfaceContext.method, + accountAddress: interfaceContext.account.address, + transaction: interfaceContext.transaction, + scope: interfaceContext.scope, + origin: interfaceContext.origin, + account: interfaceContext.account, + }); + + const updatedInterfaceContextFinal = + await getInterfaceContextOrThrow( + confirmationInterfaceId, + ); + + // Update the current context with the new rates + const updatedInterfaceContext = { + ...updatedInterfaceContextFinal, + scanFetchStatus: 'fetched' as const, + scan, + }; + + logger.info( + `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] New scan fetched`, + ); + + await updateInterface( + confirmationInterfaceId, + , + updatedInterfaceContext, + ); + + logger.info( + `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event suceeded`, + ); } catch (error) { - logger.warn( + const fetchedInterfaceContext = + await getInterfaceContextOrThrow( + confirmationInterfaceId, + ); + + const fetchingConfirmationContext = { + ...fetchedInterfaceContext, + scanFetchStatus: 'fetched', + } as ConfirmTransactionRequestContext; + + await updateInterface( + confirmationInterfaceId, + , + fetchingConfirmationContext, + ); + + logger.info( { error }, - `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event failed`, + `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Could not update the interface. But rolled back status to fetched.`, ); } + // } catch (error) { + // logger.warn( + // { error }, + // `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event failed`, + // ); + // } }; From e892ffae1ce308c825c6cab23fec354b703bdd8b Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 14:36:37 +0200 Subject: [PATCH 15/20] fix: review --- packages/snap/snap.manifest.json | 2 +- .../refreshConfirmationEstimation.tsx | 7 - .../backgroundEvents/refreshSend.tsx | 149 +++++++++--------- 3 files changed, 75 insertions(+), 83 deletions(-) diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index 7f9ed7ac9..fda094055 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "aqOdyICdZWwwirkDtw8MwwMHNiqT/nJblgJCwYCyppw=", + "shasum": "k8Iw/hr3sRgRGK4eaVRyFUBwcXUL75cbze8Er3BlaSk=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx index ae8ac7f4b..d0a9111d8 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx @@ -18,7 +18,6 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { '[refreshConfirmationEstimation]', ); - // try { logger.info( `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event triggered`, ); @@ -143,10 +142,4 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Could not update the interface. But rolled back status to fetched.`, ); } - // } catch (error) { - // logger.warn( - // { error }, - // `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event failed`, - // ); - // } }; diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx index c31479133..79876fb62 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx @@ -17,88 +17,87 @@ import { ScheduleBackgroundEventMethod } from './ScheduleBackgroundEventMethod'; export const refreshSend: OnCronjobHandler = async () => { const logger = createPrefixedLogger(baseLogger, '[refreshSend]'); - try { + // try { + logger.info( + `[${ScheduleBackgroundEventMethod.RefreshSend}] Background event triggered`, + ); + + const [assets, mapInterfaceNameToId, preferences] = await Promise.all([ + assetsService.getAll(), + state.getKey( + 'mapInterfaceNameToId', + ), + getPreferences().catch(() => DEFAULT_SEND_CONTEXT.preferences), + ]); + + const assetTypes = assets.flatMap((asset) => asset.assetType); + + const sendFormInterfaceId = mapInterfaceNameToId?.[SEND_FORM_INTERFACE_NAME]; + + // Don't do anything if the send form interface is not open + if (!sendFormInterfaceId) { logger.info( - `[${ScheduleBackgroundEventMethod.RefreshSend}] Background event triggered`, + `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ No send form interface found`, ); + return; + } - const [assets, mapInterfaceNameToId, preferences] = await Promise.all([ - assetsService.getAll(), - state.getKey( - 'mapInterfaceNameToId', - ), - getPreferences().catch(() => DEFAULT_SEND_CONTEXT.preferences), - ]); - - const assetTypes = assets.flatMap((asset) => asset.assetType); - - const sendFormInterfaceId = - mapInterfaceNameToId?.[SEND_FORM_INTERFACE_NAME]; - - // Don't do anything if the send form interface is not open - if (!sendFormInterfaceId) { - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ No send form interface found`, - ); - return; - } - - // Schedule the next run - await snap.request({ - method: 'snap_scheduleBackgroundEvent', - params: { duration: 'PT30S', request: { method: 'refreshSend' } }, - }); - - // First, fetch the token prices - const tokenPrices = await priceApiClient.getMultipleSpotPrices( - assetTypes, - preferences.currency, - ); + // Schedule the next run + await snap.request({ + method: 'snap_scheduleBackgroundEvent', + params: { duration: 'PT30S', request: { method: 'refreshSend' } }, + }); - // Save them in the state - await state.setKey('tokenPrices', tokenPrices); - - // Get the current context - const interfaceContext = - await getInterfaceContextOrThrow(sendFormInterfaceId); - - // We only want to refresh the token prices when the user is in the transaction confirmation stage - if (interfaceContext.stage !== 'transaction-confirmation') { - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ Not in transaction confirmation stage`, - ); - return; - } - - if (!interfaceContext.assets) { - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ No assets found`, - ); - return; - } - - // Update the current context with the new rates - const updatedInterfaceContext = { - ...interfaceContext, - tokenPrices: { - ...interfaceContext.tokenPrices, - ...tokenPrices, - }, - }; - - await updateInterface( - sendFormInterfaceId, - , - updatedInterfaceContext, - ); + // First, fetch the token prices + const tokenPrices = await priceApiClient.getMultipleSpotPrices( + assetTypes, + preferences.currency, + ); + // Save them in the state + await state.setKey('tokenPrices', tokenPrices); + + // Get the current context + const interfaceContext = + await getInterfaceContextOrThrow(sendFormInterfaceId); + + // We only want to refresh the token prices when the user is in the transaction confirmation stage + if (interfaceContext.stage !== 'transaction-confirmation') { logger.info( - `[${ScheduleBackgroundEventMethod.RefreshSend}] ✅ Background event suceeded`, + `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ Not in transaction confirmation stage`, ); - } catch (error) { - logger.warn( - { error }, - `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ Background event failed`, + return; + } + + if (!interfaceContext.assets) { + logger.info( + `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ No assets found`, ); + return; } + + // Update the current context with the new rates + const updatedInterfaceContext = { + ...interfaceContext, + tokenPrices: { + ...interfaceContext.tokenPrices, + ...tokenPrices, + }, + }; + + await updateInterface( + sendFormInterfaceId, + , + updatedInterfaceContext, + ); + + logger.info( + `[${ScheduleBackgroundEventMethod.RefreshSend}] ✅ Background event suceeded`, + ); + // } catch (error) { + // logger.warn( + // { error }, + // `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ Background event failed`, + // ); + // } }; From 29542941a8ac7576388e5d4b6b149d203a6a9733 Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 14:40:39 +0200 Subject: [PATCH 16/20] chore: clean up --- packages/snap/snap.manifest.json | 10 +++------- .../onCronjob/backgroundEvents/refreshSend.tsx | 7 ------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index fda094055..10416f6af 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "k8Iw/hr3sRgRGK4eaVRyFUBwcXUL75cbze8Er3BlaSk=", + "shasum": "TPLZ0K2+SJggfRDyAZYoCiBb366km4g+oCOmvu/wmlU=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -19,8 +19,7 @@ "locales": ["locales/en.json"] }, "initialConnections": { - "https://portfolio.metamask.io": {}, - "http://localhost:3000": {} + "https://portfolio.metamask.io": {} }, "initialPermissions": { "endowment:rpc": { @@ -28,10 +27,7 @@ "snaps": false }, "endowment:keyring": { - "allowedOrigins": [ - "https://portfolio.metamask.io", - "http://localhost:3000" - ] + "allowedOrigins": ["https://portfolio.metamask.io"] }, "snap_getBip32Entropy": [ { diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx index 79876fb62..766da4f31 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx @@ -17,7 +17,6 @@ import { ScheduleBackgroundEventMethod } from './ScheduleBackgroundEventMethod'; export const refreshSend: OnCronjobHandler = async () => { const logger = createPrefixedLogger(baseLogger, '[refreshSend]'); - // try { logger.info( `[${ScheduleBackgroundEventMethod.RefreshSend}] Background event triggered`, ); @@ -94,10 +93,4 @@ export const refreshSend: OnCronjobHandler = async () => { logger.info( `[${ScheduleBackgroundEventMethod.RefreshSend}] ✅ Background event suceeded`, ); - // } catch (error) { - // logger.warn( - // { error }, - // `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ Background event failed`, - // ); - // } }; From 43e9e99824703c76899e9cc922af109ee7c4eccb Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 15:04:21 +0200 Subject: [PATCH 17/20] chore: clean up --- .../refreshConfirmationEstimation.tsx | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx index d0a9111d8..56568730f 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx @@ -10,7 +10,6 @@ import { updateInterface, } from '../../../utils/interface'; import baseLogger, { createPrefixedLogger } from '../../../utils/logger'; -import { ScheduleBackgroundEventMethod } from './ScheduleBackgroundEventMethod'; export const refreshConfirmationEstimation: OnCronjobHandler = async () => { const logger = createPrefixedLogger( @@ -18,9 +17,7 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { '[refreshConfirmationEstimation]', ); - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event triggered`, - ); + logger.info(`Background event triggered`); const mapInterfaceNameToId = (await state.getKey( @@ -32,9 +29,7 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { // Don't do anything if the confirmation interface is not open if (!confirmationInterfaceId) { - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] No interface context found`, - ); + logger.info(`No interface context found`); return; } @@ -61,17 +56,13 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { !interfaceContext.scope || !interfaceContext.method ) { - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Context is missing required fields`, - ); + logger.info(`Context is missing required fields`); return; } // Skip transaction simulation if the preference is disabled if (!interfaceContext.preferences?.simulateOnChainActions) { - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Transaction simulation is disabled in preferences`, - ); + logger.info(`Transaction simulation is disabled in preferences`); return; } @@ -107,9 +98,7 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { scan, }; - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] New scan fetched`, - ); + logger.info(`New scan fetched`); await updateInterface( confirmationInterfaceId, @@ -117,9 +106,7 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { updatedInterfaceContext, ); - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Background event suceeded`, - ); + logger.info(`Background event suceeded`); } catch (error) { const fetchedInterfaceContext = await getInterfaceContextOrThrow( @@ -139,7 +126,7 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { logger.info( { error }, - `[${ScheduleBackgroundEventMethod.RefreshConfirmationEstimation}] Could not update the interface. But rolled back status to fetched.`, + `Could not update the interface. But rolled back status to fetched.`, ); } }; From f60b3b07b3f207be7e509143baa23c254133a6cb Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 15:17:58 +0200 Subject: [PATCH 18/20] feat: parallelize requests --- .../refreshConfirmationEstimation.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx index 56568730f..a76d6c45b 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshConfirmationEstimation.tsx @@ -77,19 +77,19 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { fetchingConfirmationContext, ); - const scan = await transactionScanService.scanTransaction({ - method: interfaceContext.method, - accountAddress: interfaceContext.account.address, - transaction: interfaceContext.transaction, - scope: interfaceContext.scope, - origin: interfaceContext.origin, - account: interfaceContext.account, - }); - - const updatedInterfaceContextFinal = - await getInterfaceContextOrThrow( + const [scan, updatedInterfaceContextFinal] = await Promise.all([ + transactionScanService.scanTransaction({ + method: interfaceContext.method, + accountAddress: interfaceContext.account.address, + transaction: interfaceContext.transaction, + scope: interfaceContext.scope, + origin: interfaceContext.origin, + account: interfaceContext.account, + }), + getInterfaceContextOrThrow( confirmationInterfaceId, - ); + ), + ]); // Update the current context with the new rates const updatedInterfaceContext = { From a2c83ae4e9620a48526c47b8f90775bef372bfd7 Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 15:18:51 +0200 Subject: [PATCH 19/20] chore: shasum --- packages/snap/snap.manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index 10416f6af..4c8722f38 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "TPLZ0K2+SJggfRDyAZYoCiBb366km4g+oCOmvu/wmlU=", + "shasum": "2uvwA8qWgOkAl3nzhJNnBp3IYOYsTb5jWaANNqZnXJo=", "location": { "npm": { "filePath": "dist/bundle.js", From 144a0838e6bf7660d521992d951980de5b674f93 Mon Sep 17 00:00:00 2001 From: Xavier Brochard Date: Mon, 4 Aug 2025 15:27:49 +0200 Subject: [PATCH 20/20] chore: clean up --- packages/snap/snap.manifest.json | 2 +- .../backgroundEvents/refreshSend.tsx | 21 +++++-------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index 4c8722f38..d6a3f8c92 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-solana-wallet.git" }, "source": { - "shasum": "2uvwA8qWgOkAl3nzhJNnBp3IYOYsTb5jWaANNqZnXJo=", + "shasum": "8ufRG4iBVewqxExSv+Ri41hNIHkWbdWamb0dy9J08Cw=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx index 766da4f31..86be25734 100644 --- a/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx +++ b/packages/snap/src/core/handlers/onCronjob/backgroundEvents/refreshSend.tsx @@ -12,14 +12,11 @@ import { updateInterface, } from '../../../utils/interface'; import baseLogger, { createPrefixedLogger } from '../../../utils/logger'; -import { ScheduleBackgroundEventMethod } from './ScheduleBackgroundEventMethod'; export const refreshSend: OnCronjobHandler = async () => { const logger = createPrefixedLogger(baseLogger, '[refreshSend]'); - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshSend}] Background event triggered`, - ); + logger.info(`Background event triggered`); const [assets, mapInterfaceNameToId, preferences] = await Promise.all([ assetsService.getAll(), @@ -35,9 +32,7 @@ export const refreshSend: OnCronjobHandler = async () => { // Don't do anything if the send form interface is not open if (!sendFormInterfaceId) { - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ No send form interface found`, - ); + logger.info(`No send form interface found`); return; } @@ -62,16 +57,12 @@ export const refreshSend: OnCronjobHandler = async () => { // We only want to refresh the token prices when the user is in the transaction confirmation stage if (interfaceContext.stage !== 'transaction-confirmation') { - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ Not in transaction confirmation stage`, - ); + logger.info(`❌ Not in transaction confirmation stage`); return; } if (!interfaceContext.assets) { - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshSend}] ❌ No assets found`, - ); + logger.info(`❌ No assets found`); return; } @@ -90,7 +81,5 @@ export const refreshSend: OnCronjobHandler = async () => { updatedInterfaceContext, ); - logger.info( - `[${ScheduleBackgroundEventMethod.RefreshSend}] ✅ Background event suceeded`, - ); + logger.info(`✅ Background event suceeded`); };