Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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/smart-accounts-kit/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Use `@metamask/utils` for x402 address checksum validation instead of `viem`. ([#246](https://github.com/MetaMask/smart-accounts-kit/pull/246))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Use `@metamask/utils` for x402 address checksum validation instead of `viem`. ([#246](https://github.com/MetaMask/smart-accounts-kit/pull/246))

- Bumped @metamask/delegation-abis from `^1.0.0` to `^1.1.0` ([#234](https://github.com/MetaMask/smart-accounts-kit/pull/234))
- Bumped @metamask/delegation-core from `^2.1.0` to `^2.2.1` ([#234](https://github.com/MetaMask/smart-accounts-kit/pull/234), [#240](https://github.com/MetaMask/smart-accounts-kit/pull/240))
- Bumped @metamask/delegation-deployments from `^1.3.0` to `^1.4.0` ([#234](https://github.com/MetaMask/smart-accounts-kit/pull/234))
Expand Down
4 changes: 4 additions & 0 deletions packages/x402/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Use `@metamask/utils` for Ethereum address checksum validation and drop `viem` as a package dependency. ([#246](https://github.com/MetaMask/smart-accounts-kit/pull/246))

## [0.1.0]

### Added
Expand Down
7 changes: 4 additions & 3 deletions packages/x402/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,12 @@
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"dependencies": {
"@metamask/utils": "^11.4.0"
},
"peerDependencies": {
"@x402/core": "^2.12.0",
"@x402/evm": "^2.12.0",
"viem": "^2.31.4"
"@x402/evm": "^2.12.0"
},
"devDependencies": {
"@metamask/auto-changelog": "^5.0.2",
Expand All @@ -68,7 +70,6 @@
"prettier": "^3.5.3",
"tsup": "^8.5.0",
"typescript": "5.5.4",
"viem": "2.31.4",
"vitest": "^3.2.4"
}
}
31 changes: 31 additions & 0 deletions packages/x402/src/ethereum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
getChecksumAddress,
isHexChecksumAddress,
type Hex,
} from '@metamask/utils';

/**
* Validate and normalize an Ethereum address to its EIP-55 checksum form.
*
* @param value - Address string to normalize.
* @returns The checksummed address.
*/
export function getAddress(value: string): Hex {
if (!isHexChecksumAddress(value)) {
throw new Error('Invalid Ethereum address');
}

const lowerAddress = value.toLowerCase();
const upperAddress = `0x${value.slice(2).toUpperCase()}`;
const checksummedAddress = getChecksumAddress(value);

if (
value !== lowerAddress &&
value !== upperAddress &&
value !== checksummedAddress
Comment thread
cursor[bot] marked this conversation as resolved.
) {
throw new Error('Invalid Ethereum address checksum');
}

return checksummedAddress;
}
6 changes: 4 additions & 2 deletions packages/x402/src/x402Client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { type Hex, getAddress, isHex } from 'viem';
import { isStrictHexString, type Hex } from '@metamask/utils';

import { getAddress } from './ethereum';

export type x402PaymentRequirements = {
scheme: string;
Expand Down Expand Up @@ -49,7 +51,7 @@ export type x402Erc7710ClientConfig = {
function normalizeDelegationPayload(
payload: x402DelegationPaymentPayload,
): x402DelegationPaymentPayload {
if (!isHex(payload.permissionContext) || payload.permissionContext === '0x') {
if (!isStrictHexString(payload.permissionContext)) {
throw new Error(
'Invalid delegation payload: permissionContext must be non-empty hex data',
);
Expand Down
7 changes: 4 additions & 3 deletions packages/x402/src/x402Server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type Address, getAddress } from 'viem';
import type { Hex } from '@metamask/utils';

import { getAddress } from './ethereum';
import type { x402PaymentRequirements } from './x402Client';

export type x402Erc7710ServerConfig = {
Expand All @@ -14,7 +15,7 @@ export type x402Erc7710ServerConfig = {
*/
function validateFacilitatorAddresses(
publishedAddresses: unknown,
): Address[] | undefined {
): Hex[] | undefined {
if (publishedAddresses === undefined) {
return undefined;
}
Expand All @@ -31,7 +32,7 @@ function validateFacilitatorAddresses(
);
}

const normalizedAddresses: Address[] = [];
const normalizedAddresses: Hex[] = [];
const validationErrors: string[] = [];

publishedAddresses.forEach((address, index) => {
Expand Down
28 changes: 28 additions & 0 deletions packages/x402/test/x402Server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,20 @@ describe('x402Erc7710Server', () => {
]);
});

it('normalizes all-uppercase facilitatorAddresses', async () => {
const server = new x402Erc7710Server();

const result = await server.enhancePaymentRequirements(baseRequirements, {
extra: {
facilitatorAddresses: ['0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'],
},
});

expect(result.extra?.facilitatorAddresses).toEqual([
'0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa',
]);
});

it('throws when facilitatorAddresses is not an array', async () => {
const server = new x402Erc7710Server();

Expand Down Expand Up @@ -156,4 +170,18 @@ describe('x402Erc7710Server', () => {
'Invalid facilitatorAddresses specified: facilitatorAddresses[0] must be a string; facilitatorAddresses[1] is not a valid address: "not-an-address"',
);
});

it('throws for invalid mixed-case facilitatorAddress checksums', async () => {
const server = new x402Erc7710Server();

await expect(
server.enhancePaymentRequirements(baseRequirements, {
extra: {
facilitatorAddresses: ['0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAB'],
},
}),
).rejects.toThrow(
'Invalid facilitatorAddresses specified: facilitatorAddresses[0] is not a valid address: "0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAB"',
);
});
});
3 changes: 1 addition & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -862,18 +862,17 @@ __metadata:
resolution: "@metamask/x402@workspace:packages/x402"
dependencies:
"@metamask/auto-changelog": "npm:^5.0.2"
"@metamask/utils": "npm:^11.4.0"
"@x402/core": "npm:^2.12.0"
"@x402/evm": "npm:^2.12.0"
eslint: "npm:^9.39.2"
prettier: "npm:^3.5.3"
tsup: "npm:^8.5.0"
typescript: "npm:5.5.4"
viem: "npm:2.31.4"
vitest: "npm:^3.2.4"
peerDependencies:
"@x402/core": ^2.12.0
"@x402/evm": ^2.12.0
viem: ^2.31.4
languageName: unknown
linkType: soft

Expand Down