Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/stellar-wallet-snap/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add `signProofOfOwnershipBatch` for signing multiple proof-of-ownership messages in one request. ([#267](https://github.com/MetaMask/internal-snaps/pull/267))
- Add `signProofOfOwnership` client request for silent proof-of-ownership signing (SEP-0053) ([#186](https://github.com/MetaMask/internal-snaps/pull/186))
- Add `exportAccount` keyring method for base32 Stellar secret-seed export ([#187](https://github.com/MetaMask/internal-snaps/pull/187))
- Add `TrustlineExceedLimitException` for send simulation when a payment would exceed the destination trustline limit (previously a generic `TransactionValidationException`) ([#185](https://github.com/MetaMask/internal-snaps/pull/185))
Expand Down
2 changes: 1 addition & 1 deletion packages/stellar-wallet-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/internal-snaps.git"
},
"source": {
"shasum": "0NhnLQaV7sW0XW5aSbSQMtx4aImB3rToaZugqGZUc9w=",
"shasum": "SHnUG47ZILT1CsFbGHOFPRyGwRTlXQwuuMiX/U4pjMs=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
9 changes: 9 additions & 0 deletions packages/stellar-wallet-snap/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { OnAddressInputHandler } from './handlers/clientRequest/onAddressInput';
import { OnAmountInputHandler } from './handlers/clientRequest/onAmountInput';
import { SignAndSendTransactionHandler } from './handlers/clientRequest/signAndSendTransaction';
import { SignProofOfOwnershipHandler } from './handlers/clientRequest/signProofOfOwnership';
import { SignProofOfOwnershipBatchHandler } from './handlers/clientRequest/signProofOfOwnershipBatch';
import type { ICronjobRequestHandler } from './handlers/cronjob/api';
import { BackgroundEventMethod } from './handlers/cronjob/api';
import {
Expand Down Expand Up @@ -298,6 +299,12 @@ const signProofOfOwnershipHandler = new SignProofOfOwnershipHandler({
accountResolver,
});

const signProofOfOwnershipBatchHandler = new SignProofOfOwnershipBatchHandler({
logger,
accountService,
walletService,
});

const clientRequestMethodHandlers: Record<
ClientRequestMethod,
IClientRequestHandler
Expand All @@ -309,6 +316,8 @@ const clientRequestMethodHandlers: Record<
[ClientRequestMethod.SignAndSendTransaction]: signAndSendTransactionHandler,
[ClientRequestMethod.ComputeFee]: computeFeeHandler,
[ClientRequestMethod.SignProofOfOwnership]: signProofOfOwnershipHandler,
[ClientRequestMethod.SignProofOfOwnershipBatch]:
signProofOfOwnershipBatchHandler,
};

const clientRequestHandler = new ClientRequestHandler({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
ConfirmSendJsonRpcResponseStruct,
SignAndSendTransactionJsonRpcRequestStruct,
SignAndSendTransactionJsonRpcResponseStruct,
SignProofOfOwnershipBatchJsonRpcRequestStruct,
SignProofOfOwnershipBatchJsonRpcResponseStruct,
SignProofOfOwnershipJsonRpcRequestStruct,
SignProofOfOwnershipJsonRpcResponseStruct,
} from './api';
Expand Down Expand Up @@ -1072,3 +1074,90 @@ describe('SignProofOfOwnershipJsonRpcResponseStruct', () => {
).toThrow(StructError);
});
});

describe('SignProofOfOwnershipBatchJsonRpcRequestStruct', () => {
const nonce = 'a1b2c3d4e5f6789012345678';

it('accepts a valid signProofOfOwnershipBatch request', () => {
expect(() =>
assert(
{
jsonrpc: '2.0',
id: 1,
method: ClientRequestMethod.SignProofOfOwnershipBatch,
params: {
items: [
{
accountId,
message: `metamask:proof-of-ownership:${nonce}:${stellarAddress}`,
},
],
},
},
SignProofOfOwnershipBatchJsonRpcRequestStruct,
),
).not.toThrow();
});

it.each([
{
method: ClientRequestMethod.SignProofOfOwnership,
params: { items: [] },
},
{
method: ClientRequestMethod.SignProofOfOwnershipBatch,
params: {},
},
{
method: ClientRequestMethod.SignProofOfOwnershipBatch,
params: { items: [{ accountId }] },
},
{
method: ClientRequestMethod.SignProofOfOwnershipBatch,
params: { items: [{ accountId: 'not-a-uuid', message: 'message' }] },
},
])(
'rejects an invalid signProofOfOwnershipBatch request',
({ method, params }) => {
expect(() =>
assert(
{ jsonrpc: '2.0', id: 1, method, params },
SignProofOfOwnershipBatchJsonRpcRequestStruct,
),
).toThrow(StructError);
},
);
});

describe('SignProofOfOwnershipBatchJsonRpcResponseStruct', () => {
it('accepts per-item success and error results', () => {
expect(() =>
assert(
{
results: [
{
accountId,
signature: `0x${'ab'.repeat(64)}`,
},
{
accountId: '22222222-2222-4222-8222-222222222222',
error: 'Account not found',
},
],
},
SignProofOfOwnershipBatchJsonRpcResponseStruct,
),
).not.toThrow();
});

it.each([
{},
{ results: [{ accountId, signature: 'not-a-signature' }] },
{ results: [{ accountId, error: 123 }] },
{ results: [{ accountId: 'not-a-uuid', error: 'bad id' }] },
])('rejects an invalid signProofOfOwnershipBatch response', (response) => {
expect(() =>
assert(response, SignProofOfOwnershipBatchJsonRpcResponseStruct),
).toThrow(StructError);
});
});
75 changes: 74 additions & 1 deletion packages/stellar-wallet-snap/src/handlers/clientRequest/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { AssetStruct, FeeType } from '@metamask/keyring-api';
import { UuidStruct } from '@metamask/snap-networks-utils';
import {
ProofOfOwnershipBatchErrorStruct,
ProofOfOwnershipBatchRequestItemStruct,
ProofOfOwnershipBatchRequestParamsStruct,
UuidStruct,
} from '@metamask/snap-networks-utils';
import type { Infer } from '@metamask/superstruct';
import {
enums,
Expand Down Expand Up @@ -55,6 +60,11 @@ export const ClientRequestMethod = {
* SIP-31 client-only.
*/
SignProofOfOwnership: 'signProofOfOwnership',
/**
* Silent batch proof-of-ownership signing for
* `@metamask/profile-metrics-controller`. SIP-31 client-only.
*/
SignProofOfOwnershipBatch: 'signProofOfOwnershipBatch',
/** -------------------------------- Stellar Specific -------------------------------- */
ChangeTrustOpt: 'changeTrustOpt',
} as const;
Expand Down Expand Up @@ -456,6 +466,55 @@ export const SignProofOfOwnershipJsonRpcResponseStruct = object({
signature: pattern(string(), /^0x[0-9a-f]{128}$/u),
});

/**
* Validation struct for one signProofOfOwnershipBatch request item.
*
* Messages are validated inside the handler so invalid proof messages can be
* returned as per-item errors instead of rejecting the whole batch.
*/
export const SignProofOfOwnershipBatchJsonRpcRequestItemStruct =
ProofOfOwnershipBatchRequestItemStruct;

/**
* Validation struct for the signProofOfOwnershipBatch JSON-RPC request.
*/
export const SignProofOfOwnershipBatchJsonRpcRequestStruct = assign(
JsonRpcRequestStruct,
object({
method: literal(ClientRequestMethod.SignProofOfOwnershipBatch),
params: ProofOfOwnershipBatchRequestParamsStruct,
}),
);

/**
* Validation struct for one successful signProofOfOwnershipBatch result.
*/
export const SignProofOfOwnershipBatchSuccessStruct = object({
accountId: UuidStruct,
signature: pattern(string(), /^0x[0-9a-f]{128}$/u),
});

/**
* Validation struct for one failed signProofOfOwnershipBatch result.
*/
export const SignProofOfOwnershipBatchErrorStruct =
ProofOfOwnershipBatchErrorStruct;

/**
* Validation struct for one signProofOfOwnershipBatch result.
*/
export const SignProofOfOwnershipBatchItemResponseStruct = union([
SignProofOfOwnershipBatchSuccessStruct,
SignProofOfOwnershipBatchErrorStruct,
]);

/**
* Validation struct for the signProofOfOwnershipBatch JSON-RPC response.
*/
export const SignProofOfOwnershipBatchJsonRpcResponseStruct = object({
results: array(SignProofOfOwnershipBatchItemResponseStruct),
});

/**
* A JSON-RPC request with an account resolve parameter.
*/
Expand Down Expand Up @@ -561,3 +620,17 @@ export type SignProofOfOwnershipJsonRpcRequest = Infer<
export type SignProofOfOwnershipJsonRpcResponse = Infer<
typeof SignProofOfOwnershipJsonRpcResponseStruct
>;

/**
* Type for the signProofOfOwnershipBatch JSON-RPC request.
*/
export type SignProofOfOwnershipBatchJsonRpcRequest = Infer<
typeof SignProofOfOwnershipBatchJsonRpcRequestStruct
>;

/**
* Type for the signProofOfOwnershipBatch JSON-RPC response.
*/
export type SignProofOfOwnershipBatchJsonRpcResponse = Infer<
typeof SignProofOfOwnershipBatchJsonRpcResponseStruct
>;
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './changeTrustOpt';
export * from './clientRequest';
export * from './api';
export * from './signProofOfOwnershipBatch';
export type { IClientRequestHandler } from './base';
Loading