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
2 changes: 1 addition & 1 deletion packages/smart-accounts-kit/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Experimental `createx402DelegationProvider` added to @metamask/smart-accounts-kit/experimental ([#248](https://github.com/MetaMask/smart-accounts-kit/pull/248))
- Experimental `createx402DelegationProvider` added to @metamask/smart-accounts-kit/experimental ([#248](https://github.com/MetaMask/smart-accounts-kit/pull/248), [#251](https://github.com/MetaMask/smart-accounts-kit/pull/251))
- New `generateSalt` function added to @metamask/smart-accounts-kit/utils ([#248](https://github.com/MetaMask/smart-accounts-kit/pull/248))
- ERC-7715 `token-approval-revocation` permission type ([#226](https://github.com/MetaMask/smart-accounts-kit/pull/226), [#237](https://github.com/MetaMask/smart-accounts-kit/pull/237))
- `CaveatBuilder` for `ApprovalRevocationEnforcer`, deployment address added to `SmartAccountsEnvironment` ([#226](https://github.com/metamask/smart-accounts-kit/pull/226), [#237](https://github.com/MetaMask/smart-accounts-kit/pull/237))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ export function createx402DelegationProvider(
): Promise<x402DelegationProviderPaymentPayload> => {
const {
account,
createDelegationConfig,
delegationManager,
existingDelegations,
createDelegationConfig,
rootDelegator,
} = await resolveDelegationCreationContext(config, requirements);

const delegation = createOpenDelegation(createDelegationConfig);
Expand Down Expand Up @@ -76,7 +77,7 @@ export function createx402DelegationProvider(
return {
delegationManager,
permissionContext,
delegator: delegation.delegator,
delegator: rootDelegator,
};
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ type EnsureExpirySufficientlyConstrainedParams = {
*/
export type DelegationCreationContext = {
account: Account;
createDelegationConfig: Parameters<typeof createOpenDelegation>[0];
delegationManager: Address;
existingDelegations: Delegation[];
createDelegationConfig: Parameters<typeof createOpenDelegation>[0];
rootDelegator: Address;
};

/**
Expand Down Expand Up @@ -552,10 +553,14 @@ export const resolveDelegationCreationContext = async (
};
}

const rootDelegator =
existingDelegations[existingDelegations.length - 1]?.delegator ?? from;

return {
account,
createDelegationConfig,
delegationManager,
existingDelegations,
createDelegationConfig,
rootDelegator,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ const mockPayeeEnforcer = '0x2000000000000000000000000000000000000004' as Hex;
const mockTimestampEnforcer =
'0x2000000000000000000000000000000000000005' as Hex;
const mockDelegator = '0x3000000000000000000000000000000000000003' as Hex;
const mockRootDelegator = '0x3100000000000000000000000000000000000003' as Hex;
const mockSignature = '0xabc123' as Hex;
const mockTypedData = { domain: {}, message: {} };
const mockPermissionContext = '0xfeed' as Hex;
const mockParentPermissionContext = '0xbeef' as Hex;
const mockAllowedCalldataTerms = '0x3333' as Hex;
const mockGeneratedSalt =
'0x1111111111111111111111111111111111111111111111111111111111111111' as Hex;
Expand Down Expand Up @@ -141,7 +143,16 @@ describe('createx402DelegationProvider', () => {
mockTypedData,
);
delegationMocks.encodeDelegations.mockReturnValue(mockPermissionContext);
delegationMocks.decodeDelegations.mockReturnValue([]);
delegationMocks.decodeDelegations.mockReturnValue([
{
delegate: '0xb00000000000000000000000000000000000000b',
delegator: mockRootDelegator,
authority: mockAuthority,
caveats: [],
salt: '0x44',
signature: '0x55',
},
]);
});

it('creates and signs a delegation using default from/salt values', async () => {
Expand All @@ -151,6 +162,7 @@ describe('createx402DelegationProvider', () => {
account,
environment,
caveats: [],
parentPermissionContext: mockParentPermissionContext,
});

const result = await provider(mockRequirements);
Expand Down Expand Up @@ -182,16 +194,19 @@ describe('createx402DelegationProvider', () => {
},
);
expect(account.signTypedData).toHaveBeenCalledWith(mockTypedData);
const decodedDelegations =
delegationMocks.decodeDelegations.mock.results[0]?.value ?? [];
expect(delegationMocks.encodeDelegations).toHaveBeenCalledWith([
{
...delegationMocks.createOpenDelegation.mock.results[0]?.value,
signature: mockSignature,
},
...decodedDelegations,
]);
expect(result).toStrictEqual({
delegationManager: mockDelegationManager,
permissionContext: mockPermissionContext,
delegator: mockDelegator,
delegator: mockRootDelegator,
});
});

Expand All @@ -202,6 +217,7 @@ describe('createx402DelegationProvider', () => {
account,
environment,
caveats: [],
parentPermissionContext: mockParentPermissionContext,
});

await provider({
Expand All @@ -223,6 +239,7 @@ describe('createx402DelegationProvider', () => {
account,
environment,
caveats: [],
parentPermissionContext: mockParentPermissionContext,
});

await expect(
Expand All @@ -240,6 +257,7 @@ describe('createx402DelegationProvider', () => {
const provider = createx402DelegationProvider({
account,
environment: createMockEnvironment(),
parentPermissionContext: mockParentPermissionContext,
} as x402DelegationProviderConfig);

await expect(provider(mockRequirements)).rejects.toThrow(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ describe('x402DelegationProviderUtils', () => {
environment: baseEnvironment,
}),
);
expect(result.rootDelegator).toBe(mockAccount.address);
} finally {
getEnvironmentSpy.mockRestore();
}
Expand Down Expand Up @@ -556,6 +557,40 @@ describe('x402DelegationProviderUtils', () => {
environment: baseEnvironment,
}),
);
expect(result.rootDelegator).toBe(
'0xba000000000000000000000000000000000000ba',
);
});

it('uses from as rootDelegator when parentPermissionContext is not provided', async () => {
const from = '0xca000000000000000000000000000000000000ca' as const;

const result = await resolveDelegationCreationContext(
{
account: mockAccount,
environment: baseEnvironment,
caveats: [],
from,
salt: `0x${'99'.repeat(32)}`,
},
{
scheme: 'exact',
network: 'eip155:1',
asset: facilitatorA,
amount: '1',
payTo: payee,
maxTimeoutSeconds: 120,
extra: { facilitatorAddresses: [facilitatorA] },
},
);

expect(result.existingDelegations).toStrictEqual([]);
expect(result.rootDelegator).toBe(from);
expect(result.createDelegationConfig).toEqual(
expect.objectContaining({
from,
}),
);
});

it('uses deferred caveats and deferred parent permission context', async () => {
Expand Down Expand Up @@ -652,12 +687,14 @@ describe('x402DelegationProviderUtils', () => {
});

it('does not throw when facilitators are missing and redeemers are optional', async () => {
const parentDelegation = makeDelegation([]);
await expect(
resolveDelegationCreationContext(
{
account: mockAccount,
environment: baseEnvironment,
salt: `0x${'33'.repeat(32)}`,
parentPermissionContext: [parentDelegation],
},
{
scheme: 'exact',
Expand Down Expand Up @@ -697,11 +734,13 @@ describe('x402DelegationProviderUtils', () => {
});

it('adds redeemer caveat from redeemers config addresses when facilitators are missing', async () => {
const parentDelegation = makeDelegation([]);
const result = await resolveDelegationCreationContext(
{
account: mockAccount,
environment: baseEnvironment,
salt: `0x${'33'.repeat(32)}`,
parentPermissionContext: [parentDelegation],
redeemers: {
requireRedeemers: true,
addresses: [facilitatorB],
Expand Down Expand Up @@ -732,6 +771,7 @@ describe('x402DelegationProviderUtils', () => {
});

it('resolves deferred redeemers configuration and addresses', async () => {
const parentDelegation = makeDelegation([]);
const deferredRedeemerAddresses = vi.fn(async () => [facilitatorB]);
const deferredRedeemersConfig = vi.fn(async () => ({
requireRedeemers: true,
Expand All @@ -743,6 +783,7 @@ describe('x402DelegationProviderUtils', () => {
account: mockAccount,
environment: baseEnvironment,
salt: `0x${'33'.repeat(32)}`,
parentPermissionContext: [parentDelegation],
redeemers: deferredRedeemersConfig,
},
{
Expand Down Expand Up @@ -775,6 +816,7 @@ describe('x402DelegationProviderUtils', () => {
it('resolves deferred expirySeconds and applies timestamp caveat', async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2024-01-01T00:00:00Z'));
const parentDelegation = makeDelegation([]);

const deferredExpirySeconds = vi.fn(async () => 120);
const result = await resolveDelegationCreationContext(
Expand All @@ -784,6 +826,7 @@ describe('x402DelegationProviderUtils', () => {
caveats: [],
salt: `0x${'66'.repeat(32)}`,
expirySeconds: deferredExpirySeconds,
parentPermissionContext: [parentDelegation],
},
{
scheme: 'exact',
Expand Down Expand Up @@ -816,6 +859,7 @@ describe('x402DelegationProviderUtils', () => {
it('resolves synchronous deferred expirySeconds and applies timestamp caveat', async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2024-01-01T00:00:00Z'));
const parentDelegation = makeDelegation([]);

const deferredExpirySeconds = vi.fn(() => 90);
const result = await resolveDelegationCreationContext(
Expand All @@ -825,6 +869,7 @@ describe('x402DelegationProviderUtils', () => {
caveats: [],
salt: `0x${'66'.repeat(32)}`,
expirySeconds: deferredExpirySeconds,
parentPermissionContext: [parentDelegation],
},
{
scheme: 'exact',
Expand Down
Loading