diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index ddb80479..bc640058 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": "hFc9PK6QCElAWB+pnkHVzW3JQVj8UNub9NozdKIxAOQ=", + "shasum": "nB+rWwqDOLneOumFj5Npqh2itldzWWFByUhA9pwWP9w=", "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": [ { @@ -73,8 +77,10 @@ "snap_manageAccounts": {}, "snap_manageState": {}, "snap_dialog": {}, - "snap_getPreferences": {} + "snap_getPreferences": {}, + "snap_confirmTransaction": {}, + "snap_updateConfirmTransaction": {} }, - "platformVersion": "10.3.0", + "platformVersion": "11.1.1", "manifestVersion": "0.1" } diff --git a/packages/snap/src/core/services/send/SendService.test.ts b/packages/snap/src/core/services/send/SendService.test.ts index 23457f7c..b12d6e2d 100644 --- a/packages/snap/src/core/services/send/SendService.test.ts +++ b/packages/snap/src/core/services/send/SendService.test.ts @@ -42,6 +42,12 @@ Object.defineProperty(globalThis, 'crypto', { }, }); +Object.defineProperty(globalThis, 'snap', { + value: { + request: jest.fn(), + }, +}); + jest.mock('@solana/kit', () => ({ ...jest.requireActual('@solana/kit'), address: jest.requireActual('@solana/kit').address, @@ -75,6 +81,7 @@ describe('SendService', () => { beforeEach(() => { jest.clearAllMocks(); + jest.mocked(snap.request).mockResolvedValue(true); mockConnection = createMockConnection(); mockCache = new InMemoryCache(mockLogger); diff --git a/packages/snap/src/core/services/send/SendService.ts b/packages/snap/src/core/services/send/SendService.ts index 00537d20..831ff8a4 100644 --- a/packages/snap/src/core/services/send/SendService.ts +++ b/packages/snap/src/core/services/send/SendService.ts @@ -14,6 +14,7 @@ import { fromTransactionToBase64String } from '../../sdk-extensions/codecs'; import type { Serializable } from '../../serialization/types'; import { solToLamports } from '../../utils/conversion'; import { createPrefixedLogger, type ILogger } from '../../utils/logger'; +import { toTokenUnits } from '../../utils/toTokenUnit'; import type { AssetsService } from '../assets'; import type { SolanaConnection } from '../connection'; import type { RecipientClassifier } from './RecipientClassifier'; @@ -131,6 +132,74 @@ export class SendService { const base64EncodedTransaction = fromTransactionToBase64String(transaction); + const accountBalances = + (await this.#assetsService.findByAccount(account)) ?? []; + const assetEntry = accountBalances.find((a) => a.assetType === assetId); + + const assetDecimals = + assetEntry && 'decimals' in assetEntry + ? assetEntry.decimals + : isNativeSend + ? 9 + : 0; + + const rawValue = isNativeSend + ? solToLamports(amount).toString() + : toTokenUnits(amount, assetDecimals).toString(); + + const sendFeeCalculator = new SendFeeCalculator(builder); + const feeRaw = sendFeeCalculator.getFee().toString(); + const confirmationId = globalThis.crypto.randomUUID(); + let customCount = 0; + + const confirmationPromise = snap.request({ + method: 'snap_confirmTransaction', + params: { + id: confirmationId, + chainId: scope, + accountId: fromAccountId, + to: toAddress, + amount: rawValue, + ...(isNativeSend ? {} : { assetId }), + fee: { + amount: feeRaw, + }, + custom: { + count: customCount, + }, + }, + }); + + const updateInterval = setInterval(() => { + customCount += 1; + + void snap + .request({ + method: 'snap_updateConfirmTransaction', + params: { + id: confirmationId, + custom: { + count: customCount, + }, + }, + }) + .catch((error) => { + this.#logger.warn( + 'Failed to update universal confirmation custom data', + error, + ); + }); + }, 1000); + + const approved = await confirmationPromise.finally(() => { + clearInterval(updateInterval); + }); + + if (!approved) { + this.#logger.log('User rejected transaction via universal confirmation'); + return { rejected: true }; + } + const keyringRequest: KeyringRequest = { id: globalThis.crypto.randomUUID(), scope,