UNPKG

@metamask/delegation-toolkit

Version:

The Delegation Toolkit built on top of Viem - a library for interacting with DeleGator Smart Accounts

481 lines (463 loc) 15.3 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _chunkCTZHYDS4cjs = require('../chunk-CTZHYDS4.cjs'); var _chunkT6PSFUOZcjs = require('../chunk-T6PSFUOZ.cjs'); var _chunk5GYFPRIZcjs = require('../chunk-5GYFPRIZ.cjs'); // src/experimental/erc7710RedeemDelegationAction.ts var _delegationabis = require('@metamask/delegation-abis'); var _viem = require('viem'); async function sendTransactionWithDelegationAction(client, args) { if (!args.to) { throw new Error( "`to` is required. `sendTransactionWithDelegation` cannot be used to deploy contracts." ); } const executions = [ _chunkT6PSFUOZcjs.createExecution.call(void 0, { target: args.to, value: args.value, callData: args.data }) ]; const calldata = _viem.encodeFunctionData.call(void 0, { abi: _delegationabis.DelegationManager.abi, functionName: "redeemDelegations", args: [ [args.permissionsContext], ["0x0000000000000000000000000000000000000000000000000000000000000000" /* SingleDefault */], _chunkT6PSFUOZcjs.encodeExecutionCalldatas.call(void 0, [executions]) ] }); const { value: _value, permissionsContext: _permissionsContext, delegationManager: _delegationManager, ...rest } = args; const hash = await client.sendTransaction({ ...rest, to: args.delegationManager, data: calldata }); return hash; } async function sendUserOperationWithDelegationAction(client, parameters) { if (parameters.accountMetadata) { const { publicClient } = parameters; const includedAccountKeys = {}; const chainId = _optionalChain([publicClient, 'access', _2 => _2.chain, 'optionalAccess', _3 => _3.id]); if (!chainId) { throw new Error("Chain ID is not set"); } const { SimpleFactory } = _chunkCTZHYDS4cjs.getDeleGatorEnvironment.call(void 0, chainId); const uniqueAccountMetadatas = parameters.accountMetadata.filter( (accountMetadata) => { if (!_viem.isAddressEqual.call(void 0, accountMetadata.factory, SimpleFactory)) { throw new Error( `Invalid accountMetadata: ${accountMetadata.factory} is not allowed.` ); } const accountKey = _viem.concat.call(void 0, [ accountMetadata.factory, accountMetadata.factoryData ]); const isDuplicate = includedAccountKeys[accountKey]; includedAccountKeys[accountKey] = true; return !isDuplicate; } ); const factoryCalls = (await Promise.all( uniqueAccountMetadatas.map(async ({ factory, factoryData }) => { const isDeployed = await publicClient.call({ to: factory, data: factoryData }).then(() => false).catch(() => true); if (isDeployed) { return void 0; } return { to: factory, value: 0n, data: factoryData }; }) )).filter((call) => call !== void 0); parameters.calls = [ ...factoryCalls, ...parameters.calls ]; } return client.sendUserOperation( parameters ); } // src/experimental/erc7715RequestExecutionPermissionsAction.ts async function erc7715RequestExecutionPermissionsAction(client, parameters) { const formattedPermissionRequest = parameters.map(formatPermissionsRequest); const result = await client.request( { method: "wallet_requestExecutionPermissions", params: formattedPermissionRequest }, { retryCount: 0 } ); if (!result) { throw new Error("Failed to grant permissions"); } return result; } function formatPermissionsRequest(parameters) { const { chainId, address, expiry, isAdjustmentAllowed } = parameters; const permissionFormatter = getPermissionFormatter( parameters.permission.type ); const signerAddress = typeof parameters.signer === "string" ? parameters.signer : parameters.signer.data.address; const rules = [ { type: "expiry", isAdjustmentAllowed, data: { timestamp: expiry } } ]; const optionalFields = { ...address ? { address } : {} }; return { ...optionalFields, chainId: _viem.toHex.call(void 0, chainId), permission: permissionFormatter({ permission: parameters.permission, isAdjustmentAllowed }), signer: { // MetaMask 7715 implementation only supports AccountSigner type: "account", data: { address: signerAddress } }, rules }; } function isDefined(value) { return value !== void 0 && value !== null; } function assertIsDefined(value, message) { if (!isDefined(value)) { throw new Error(_nullishCoalesce(message, () => ( "Invalid parameters: value is required"))); } } function toHexOrThrow(value, message) { assertIsDefined(value, message); if (typeof value === "string") { if (!_viem.isHex.call(void 0, value)) { throw new Error("Invalid parameters: invalid hex value"); } return value; } return _viem.toHex.call(void 0, value); } function getPermissionFormatter(permissionType) { switch (permissionType) { case "native-token-stream": return ({ permission, isAdjustmentAllowed }) => formatNativeTokenStreamPermission({ permission, isAdjustmentAllowed }); case "erc20-token-stream": return ({ permission, isAdjustmentAllowed }) => formatErc20TokenStreamPermission({ permission, isAdjustmentAllowed }); case "native-token-periodic": return ({ permission, isAdjustmentAllowed }) => formatNativeTokenPeriodicPermission({ permission, isAdjustmentAllowed }); case "erc20-token-periodic": return ({ permission, isAdjustmentAllowed }) => formatErc20TokenPeriodicPermission({ permission, isAdjustmentAllowed }); default: throw new Error(`Unsupported permission type: ${permissionType}`); } } function formatNativeTokenStreamPermission({ permission, isAdjustmentAllowed }) { const { data: { initialAmount, justification, maxAmount, startTime, amountPerSecond } } = permission; const optionalFields = { ...isDefined(initialAmount) && { initialAmount: toHexOrThrow(initialAmount) }, ...isDefined(maxAmount) && { maxAmount: toHexOrThrow(maxAmount) }, ...isDefined(startTime) && { startTime: Number(startTime) }, ...justification ? { justification } : {} }; return { type: "native-token-stream", data: { amountPerSecond: toHexOrThrow( amountPerSecond, "Invalid parameters: amountPerSecond is required" ), ...optionalFields }, isAdjustmentAllowed }; } function formatErc20TokenStreamPermission({ permission, isAdjustmentAllowed }) { const { data: { tokenAddress, amountPerSecond, initialAmount, startTime, maxAmount, justification } } = permission; const optionalFields = { ...isDefined(initialAmount) && { initialAmount: toHexOrThrow(initialAmount) }, ...isDefined(maxAmount) && { maxAmount: toHexOrThrow(maxAmount) }, ...isDefined(startTime) && { startTime: Number(startTime) }, ...justification ? { justification } : {} }; return { type: "erc20-token-stream", data: { tokenAddress: toHexOrThrow(tokenAddress), amountPerSecond: toHexOrThrow(amountPerSecond), ...optionalFields }, isAdjustmentAllowed }; } function formatNativeTokenPeriodicPermission({ permission, isAdjustmentAllowed }) { const { data: { periodAmount, periodDuration, startTime, justification } } = permission; const optionalFields = { ...isDefined(startTime) && { startTime: Number(startTime) }, ...justification ? { justification } : {} }; return { type: "native-token-periodic", data: { periodAmount: toHexOrThrow(periodAmount), periodDuration: Number(periodDuration), ...optionalFields }, isAdjustmentAllowed }; } function formatErc20TokenPeriodicPermission({ permission, isAdjustmentAllowed }) { const { data: { tokenAddress, periodAmount, periodDuration, startTime, justification } } = permission; const optionalFields = { ...isDefined(startTime) && { startTime: Number(startTime) }, ...justification ? { justification } : {} }; return { type: "erc20-token-periodic", data: { tokenAddress: toHexOrThrow(tokenAddress), periodAmount: toHexOrThrow(periodAmount), periodDuration: Number(periodDuration), ...optionalFields }, isAdjustmentAllowed }; } // src/experimental/delegationStorage.ts var DelegationStorageClient = class { #apiVersionPrefix = "api/v0"; #config; #fetcher; #apiUrl; constructor(config) { const { apiUrl } = config.environment; if (apiUrl.endsWith(this.#apiVersionPrefix)) { this.#apiUrl = apiUrl; } else { const separator = apiUrl.endsWith("/") ? "" : "/"; this.#apiUrl = `${apiUrl}${separator}${this.#apiVersionPrefix}`; } this.#fetcher = this.#initializeFetcher(config); this.#config = config; } /** * Initializes the fetch function for HTTP requests. * * - Uses `config.fetcher` if provided. * - Falls back to global `fetch` if available. * - Throws an error if no fetch function is available. * * @param config - Configuration object that may include a custom fetch function. * @returns The fetch function to be used for HTTP requests. * @throws Error if no fetch function is available in the environment. */ #initializeFetcher(config) { if (config.fetcher) { return config.fetcher; } else if (typeof _optionalChain([globalThis, 'optionalAccess', _4 => _4.fetch]) === "function") { return globalThis.fetch.bind(globalThis); } throw new Error( "Fetch API is not available in this environment. Please provide a fetch function in the config." ); } /** * Fetches the delegation chain from the Delegation Storage Service, ending with * the specified leaf delegation. * * @param leafDelegationOrDelegationHash - The leaf delegation, or the hash * of the leaf delegation. * @returns A promise that resolves to the delegation chain - empty array if the delegation * is not found. */ async getDelegationChain(leafDelegationOrDelegationHash) { const leafDelegationHash = typeof leafDelegationOrDelegationHash === "string" ? leafDelegationOrDelegationHash : _chunk5GYFPRIZcjs.getDelegationHashOffchain.call(void 0, leafDelegationOrDelegationHash); const response = await this.#fetcher( `${this.#apiUrl}/delegation/chain/${leafDelegationHash}`, { method: "GET", headers: { Authorization: `Bearer ${this.#config.apiKey}`, "x-api-key-id": this.#config.apiKeyId } } ); const responseData = await response.json(); if ("error" in responseData) { throw new Error( `Failed to fetch delegation chain: ${responseData.error}` ); } return responseData; } /** * Fetches the delegations from the Delegation Storage Service, either `Received` * by, or `Given` by, (or both: `All`) the specified deleGatorAddress. Defaults * to `Received`. * * @param deleGatorAddress - The deleGatorAddress to retrieve the delegations for. * @param filterMode - The DelegationStoreFilter mode - defaults to Received. * @returns A promise that resolves to the list of delegations received by the deleGatorAddress, * empty array if the delegations are not found. */ async fetchDelegations(deleGatorAddress, filterMode = "RECEIVED" /* Received */) { const response = await this.#fetcher( `${this.#apiUrl}/delegation/accounts/${deleGatorAddress}?filter=${filterMode}`, { method: "GET", headers: { Authorization: `Bearer ${this.#config.apiKey}`, "x-api-key-id": this.#config.apiKeyId } } ); const responseData = await response.json(); if ("error" in responseData) { throw new Error(`Failed to fetch delegations: ${responseData.error}`); } return responseData; } /** * Stores the specified delegation in the Delegation Storage Service. * * @param delegation - The delegation to store. * @returns A promise that resolves to the delegation hash indicating successful storage. */ async storeDelegation(delegation) { if (!delegation.signature || delegation.signature === "0x") { throw new Error("Delegation must be signed to be stored"); } const delegationHash = _chunk5GYFPRIZcjs.getDelegationHashOffchain.call(void 0, delegation); const body = JSON.stringify( { ...delegation, metadata: [] }, (_, value) => typeof value === "bigint" || typeof value === "number" ? _viem.toHex.call(void 0, value) : value, 2 ); const response = await this.#fetcher(`${this.#apiUrl}/delegation/store`, { method: "POST", headers: { Authorization: `Bearer ${this.#config.apiKey}`, "x-api-key-id": this.#config.apiKeyId, "Content-Type": "application/json" }, body }); const responseData = await response.json(); if ("error" in responseData) { throw new Error(responseData.error); } if (responseData.delegationHash !== delegationHash) { throw Error( "Failed to store the Delegation, the hash returned from the MM delegation storage API does not match the hash of the delegation" ); } return responseData.delegationHash; } }; // src/experimental/index.ts var erc7715ProviderActions = () => (client) => ({ requestExecutionPermissions: async (parameters) => { return erc7715RequestExecutionPermissionsAction( client, parameters ); } }); var erc7710WalletActions = () => (client) => ({ sendTransactionWithDelegation: async (args) => sendTransactionWithDelegationAction(client, args) }); var erc7710BundlerActions = () => (client) => ({ sendUserOperationWithDelegation: async (args) => sendUserOperationWithDelegationAction(client, args) }); exports.DelegationStorageClient = DelegationStorageClient; exports.erc7710BundlerActions = erc7710BundlerActions; exports.erc7710WalletActions = erc7710WalletActions; exports.erc7715ProviderActions = erc7715ProviderActions; exports.requestExecutionPermissions = erc7715RequestExecutionPermissionsAction; //# sourceMappingURL=index.cjs.map