Skip to content
16 changes: 11 additions & 5 deletions packages/snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -19,15 +19,19 @@
"locales": ["locales/en.json"]
},
"initialConnections": {
"https://portfolio.metamask.io": {}
"https://portfolio.metamask.io": {},
"http://localhost:3000": {}
},
"initialPermissions": {
"endowment:rpc": {
"dapps": true,
"snaps": false
},
"endowment:keyring": {
"allowedOrigins": ["https://portfolio.metamask.io"]
"allowedOrigins": [
"https://portfolio.metamask.io",
"http://localhost:3000"
]
},
"snap_getBip32Entropy": [
{
Expand Down Expand Up @@ -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"
}
7 changes: 7 additions & 0 deletions packages/snap/src/core/services/send/SendService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -75,6 +81,7 @@ describe('SendService', () => {

beforeEach(() => {
jest.clearAllMocks();
jest.mocked(snap.request).mockResolvedValue(true);

mockConnection = createMockConnection();
mockCache = new InMemoryCache(mockLogger);
Expand Down
69 changes: 69 additions & 0 deletions packages/snap/src/core/services/send/SendService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down