Skip to content
Merged
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: 2 additions & 0 deletions packages/smart-accounts-kit/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ 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))
- 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
1 change: 1 addition & 0 deletions packages/smart-accounts-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
"@metamask/delegation-abis": "^1.1.0",
"@metamask/delegation-core": "^2.2.1",
"@metamask/delegation-deployments": "^1.4.0",
"@metamask/utils": "^11.4.0",
"openapi-fetch": "^0.13.5",
"ox": "0.8.1"
},
Expand Down
9 changes: 9 additions & 0 deletions packages/smart-accounts-kit/src/experimental/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,12 @@ export {
type Environment,
type DelegationStorageConfig,
} from './delegationStorage';
export {
createx402DelegationProvider,
parseEip155ChainId,
type x402DelegationProvider,
type x402DelegationProviderConfig,
type x402DelegationProviderPaymentPayload,
type PaymentRequirements,
type MaybeDeferred,
} from './x402DelegationProvider';
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {
createOpenDelegation,
encodeDelegations,
prepareSignDelegationTypedData,
} from '../delegation';
import type {
PaymentRequirements,
x402DelegationProvider,
x402DelegationProviderConfig,
x402DelegationProviderPaymentPayload,
} from './x402DelegationProviderTypes';
import {
parseEip155ChainId,
resolveDelegationCreationContext,
} from './x402DelegationProviderUtils';

export type {
MaybeDeferred,
PaymentRequirements,
x402DelegationProvider,
x402DelegationProviderConfig,
x402DelegationProviderPaymentPayload,
} from './x402DelegationProviderTypes';

export { parseEip155ChainId } from './x402DelegationProviderUtils';

/**
* Creates a delegation provider function for x402 payment requirements.
*
* The returned provider resolves deferred config values using incoming payment
* requirements, creates an open delegation, signs it, and returns an encoded
* permission context payload.
*
* @param config - Delegation creation and signing configuration.
* @returns A provider that maps payment requirements to a signed delegation payload.
*/
export function createx402DelegationProvider(
config: x402DelegationProviderConfig,
): x402DelegationProvider {
return async (
requirements: PaymentRequirements,
): Promise<x402DelegationProviderPaymentPayload> => {
const {
account,
delegationManager,
existingDelegations,
createDelegationConfig,
} = await resolveDelegationCreationContext(config, requirements);

const delegation = createOpenDelegation(createDelegationConfig);

const chainId = parseEip155ChainId(requirements.network);

const typedData = prepareSignDelegationTypedData({
delegationManager,
chainId,
delegation,
});

if (!account.signTypedData) {
throw new Error('Account does not support signTypedData');
}

const signature = await account.signTypedData(typedData);

const signedDelegation = {
...delegation,
signature,
};

const permissionContext = encodeDelegations([
signedDelegation,
...existingDelegations,
]);

return {
delegationManager,
permissionContext,
delegator: delegation.delegator,
};
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { Account, Address, Hex } from 'viem';

import type { Caveats } from '../caveatBuilder';
import type { PermissionContext, SmartAccountsEnvironment } from '../types';

/**
* Payment requirement details supplied by an x402 server challenge.
*
* These values are used to scope and construct the delegation that will be
* returned by a {@link x402DelegationProvider}.
*/
export type PaymentRequirements = {
scheme: string;
network: string;
asset: string;
amount: string;
payTo: string;
maxTimeoutSeconds: number;
extra?: Record<string, unknown>;
};

/**
* Encoded delegation response consumed by x402 payment flows.
*
* The payload includes the delegation manager address, the encoded permission
* context to use for execution, and the delegator account that signed it.
*/
export type x402DelegationProviderPaymentPayload = {
delegationManager: Hex;
permissionContext: Hex;
delegator: Hex;
};

/**
* Value that can be provided eagerly or derived lazily from runtime requirements.
*
* @template TResult - Resolved value type.
*/
export type MaybeDeferred<TResult> =
| TResult
| ((requirements: PaymentRequirements) => Promise<TResult> | TResult);

/**
* Function that turns payment requirements into a signed delegation payload.
*/
export type x402DelegationProvider = (
paymentRequirements: PaymentRequirements,
) => Promise<x402DelegationProviderPaymentPayload>;

/**
* Configuration for redeemer constraint enforcement.
*/
export type RedeemersConfig = {
requireRedeemers: boolean;
addresses?: MaybeDeferred<Address[]>;
};

/**
* Configuration used to create a x402DelegationProvider.
*
* `account` is required and is used for signing the delegation.
*/
export type x402DelegationProviderConfig = {
account: MaybeDeferred<Account>;
environment?: MaybeDeferred<SmartAccountsEnvironment>;
from?: MaybeDeferred<Hex>;
salt?: MaybeDeferred<Hex>;
caveats?: MaybeDeferred<Caveats>;
parentPermissionContext?: MaybeDeferred<PermissionContext>;
expirySeconds?: MaybeDeferred<number>;
redeemers?: MaybeDeferred<RedeemersConfig>;
};
Loading
Loading