Skip to content

Commit 62f251f

Browse files
feat: fix sendTransfer & add sendTransfer confirmation
1 parent cc91fd6 commit 62f251f

File tree

7 files changed

+114
-16
lines changed

7 files changed

+114
-16
lines changed

packages/snap/src/entities/confirmation.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Network } from '@metamask/bitcoindevkit';
1+
import type { Network, Psbt } from '@metamask/bitcoindevkit';
22

33
import type { BitcoinAccount } from './account';
44

@@ -33,4 +33,21 @@ export type ConfirmationRepository = {
3333
message: string,
3434
origin: string,
3535
): Promise<void>;
36+
37+
/**
38+
* Inserts a send transfer confirmation interface.
39+
*
40+
* @param account - The account sending the transfer.
41+
* @param psbt - The PSBT of the transfer.
42+
* @param recipient - The recipient of the transfer.
43+
* @param recipient.address - The address of the recipient.
44+
* @param recipient.amount - The amount to send to the recipient.
45+
* @param origin - The origin of the request.
46+
*/
47+
insertSendTransfer(
48+
account: BitcoinAccount,
49+
psbt: Psbt,
50+
recipient: { address: string; amount: string },
51+
origin: string,
52+
): Promise<void>;
3653
};

packages/snap/src/entities/send-flow.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type ConfirmSendFormContext = {
1717
backgroundEventId?: string;
1818
locale: string;
1919
psbt: string;
20+
origin?: string;
2021
};
2122

2223
export type SendFormContext = {

packages/snap/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const sendFlowRepository = new JSXSendFlowRepository(snapClient, translator);
5151
const confirmationRepository = new JSXConfirmationRepository(
5252
snapClient,
5353
translator,
54+
chainClient,
5455
);
5556

5657
// Business layer

packages/snap/src/infra/jsx/unified-send-flow/UnifiedSendFormView.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,15 @@ export const UnifiedSendFormView: SnapComponent<UnifiedSendFormViewProps> = ({
3535
messages,
3636
}) => {
3737
const t = translate(messages);
38-
const { amount, exchangeRate, network, from, recipient, explorerUrl } =
39-
context;
38+
const {
39+
amount,
40+
exchangeRate,
41+
network,
42+
from,
43+
recipient,
44+
explorerUrl,
45+
origin,
46+
} = context;
4047

4148
const psbt = Psbt.from_string(context.psbt);
4249
const fee = psbt.fee().to_sat();
@@ -83,7 +90,7 @@ export const UnifiedSendFormView: SnapComponent<UnifiedSendFormViewProps> = ({
8390
<SnapText fontWeight="medium" color="alternative">
8491
{t('confirmation.requestOrigin')}
8592
</SnapText>
86-
<SnapText>MetaMask</SnapText>
93+
<SnapText>{origin ?? 'MetaMask'}</SnapText>
8794
</Box>
8895
<Box>{null}</Box>
8996
<Box alignment="space-between" direction="horizontal">

packages/snap/src/store/JSXConfirmationRepository.test.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import type { Address } from '@metamask/bitcoindevkit';
22
import type { GetPreferencesResult } from '@metamask/snaps-sdk';
33
import { mock } from 'jest-mock-extended';
44

5-
import type { SnapClient, Translator, BitcoinAccount } from '../entities';
5+
import type {
6+
SnapClient,
7+
Translator,
8+
BitcoinAccount,
9+
BlockchainClient,
10+
} from '../entities';
611
import { JSXConfirmationRepository } from './JSXConfirmationRepository';
712
import { SignMessageConfirmationView } from '../infra/jsx';
813

@@ -14,8 +19,13 @@ describe('JSXConfirmationRepository', () => {
1419
const mockMessages = { foo: { message: 'bar' } };
1520
const mockSnapClient = mock<SnapClient>();
1621
const mockTranslator = mock<Translator>();
22+
const mockChainClient = mock<BlockchainClient>();
1723

18-
const repo = new JSXConfirmationRepository(mockSnapClient, mockTranslator);
24+
const repo = new JSXConfirmationRepository(
25+
mockSnapClient,
26+
mockTranslator,
27+
mockChainClient,
28+
);
1929

2030
describe('insertSignMessage', () => {
2131
const mockAccount = mock<BitcoinAccount>({

packages/snap/src/store/JSXConfirmationRepository.tsx

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
1+
import type { Psbt } from '@metamask/bitcoindevkit';
2+
13
import type {
24
BitcoinAccount,
5+
BlockchainClient,
36
ConfirmationRepository,
7+
ConfirmSendFormContext,
48
SignMessageConfirmationContext,
59
SnapClient,
610
Translator,
711
} from '../entities';
8-
import { UserActionError } from '../entities';
12+
import { networkToCurrencyUnit, UserActionError } from '../entities';
913
import { SignMessageConfirmationView } from '../infra/jsx';
14+
import { UnifiedSendFormView } from '../infra/jsx/unified-send-flow';
1015

1116
export class JSXConfirmationRepository implements ConfirmationRepository {
1217
readonly #snapClient: SnapClient;
1318

1419
readonly #translator: Translator;
1520

16-
constructor(snapClient: SnapClient, translator: Translator) {
21+
readonly #chainClient: BlockchainClient;
22+
23+
constructor(
24+
snapClient: SnapClient,
25+
translator: Translator,
26+
chainClient: BlockchainClient,
27+
) {
1728
this.#snapClient = snapClient;
1829
this.#translator = translator;
30+
this.#chainClient = chainClient;
1931
}
2032

2133
async insertSignMessage(
@@ -48,4 +60,38 @@ export class JSXConfirmationRepository implements ConfirmationRepository {
4860
throw new UserActionError('User canceled the confirmation');
4961
}
5062
}
63+
64+
async insertSendTransfer(
65+
account: BitcoinAccount,
66+
psbt: Psbt,
67+
recipient: { address: string; amount: string },
68+
origin: string,
69+
): Promise<void> {
70+
const { locale } = await this.#snapClient.getPreferences();
71+
72+
const context: ConfirmSendFormContext = {
73+
from: account.publicAddress.toString(),
74+
explorerUrl: this.#chainClient.getExplorerUrl(account.network),
75+
network: account.network,
76+
currency: networkToCurrencyUnit[account.network],
77+
exchangeRate: undefined,
78+
recipient: recipient.address,
79+
amount: recipient.amount,
80+
locale,
81+
psbt: psbt.toString(),
82+
origin,
83+
};
84+
85+
const messages = await this.#translator.load(locale);
86+
const interfaceId = await this.#snapClient.createInterface(
87+
<UnifiedSendFormView context={context} messages={messages} />,
88+
context,
89+
);
90+
91+
const confirmed =
92+
await this.#snapClient.displayConfirmation<boolean>(interfaceId);
93+
if (!confirmed) {
94+
throw new UserActionError('User canceled the confirmation');
95+
}
96+
}
5197
}

packages/snap/src/use-cases/AccountUseCases.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -461,21 +461,37 @@ export class AccountUseCases {
461461
recipients,
462462
);
463463

464+
if (!recipients[0] || recipients.length > 1) {
465+
throw new ValidationError('There should be exactly one recipient', {
466+
recipients,
467+
});
468+
}
469+
const recipient = recipients[0];
470+
464471
const account = await this.#repository.getWithSigner(id);
465472
if (!account) {
466473
throw new NotFoundError('Account not found', { id });
467474
}
468475
this.#checkCapability(account, AccountCapability.SendTransfer);
469476

470-
// Create a template PSBT with the recipients as outputs
471-
let builder = account.buildTx();
472-
for (const { address, amount } of recipients) {
473-
builder = builder.addRecipient(amount, address);
474-
}
475-
const templatePsbt = builder.finish();
477+
const frozenUTXOs = await this.#repository.getFrozenUTXOs(account.id);
478+
const feeRateToUse = feeRate ?? (await this.getFallbackFeeRate(account));
479+
480+
// Build PSBT with the recipient as output
481+
const psbt = account
482+
.buildTx()
483+
.feeRate(feeRateToUse)
484+
.unspendable(frozenUTXOs)
485+
.addRecipient(recipient.amount, recipient.address)
486+
.finish();
487+
488+
await this.#confirmationRepository.insertSendTransfer(
489+
account,
490+
psbt,
491+
recipient,
492+
origin,
493+
);
476494

477-
// Complete the PSBT with the necessary inputs, fee rate, etc.
478-
const psbt = await this.#fillPsbt(account, templatePsbt, feeRate);
479495
const signedPsbt = account.sign(psbt);
480496
const tx = account.extractTransaction(signedPsbt);
481497
const txid = await this.#broadcast(account, tx, origin);

0 commit comments

Comments
 (0)