import { createVincentPolicy } from '@lit-protocol/vincent-ability-sdk';
import { z } from 'zod';
const vincentPolicy = createVincentPolicy({
packageName: '@example/spending-limit-policy',
abilityParamsSchema: z.object({
tokenAddress: z.string(),
amount: z.number(),
}),
userParamsSchema: z.object({
dailySpendingLimit: z.number(),
allowedTokens: z.array(z.string()).optional(),
}),
precheckAllowResultSchema: z.object({
maxDailySpendingLimit: z.number(),
currentDailySpending: z.number(),
allowedTokens: z.array(z.string()),
}),
precheckDenyResultSchema: z.object({
reason: z.string(),
maxDailySpendingLimit: z.number(),
currentDailySpending: z.number(),
allowedTokens: z.array(z.string()),
}),
precheck: async ({ abilityParams, userParams }, policyContext) => {
const { amount, tokenAddress } = abilityParams;
const { dailySpendingLimit, allowedTokens } = userParams;
const isTokenAllowed = allowedTokens?.includes(tokenAddress) ?? true;
const { isSpendingLimitExceeded, currentDailySpending } = await checkSpendingLimit(
tokenAddress,
amount,
dailySpendingLimit,
);
if (!isTokenAllowed) {
return policyContext.deny({
reason: 'Token not allowed',
maxDailySpendingLimit: dailySpendingLimit,
currentDailySpending,
allowedTokens: allowedTokens || [],
});
}
if (isSpendingLimitExceeded) {
return policyContext.deny({
reason: 'Spending limit exceeded',
maxDailySpendingLimit: dailySpendingLimit,
currentDailySpending,
allowedTokens: allowedTokens || [],
});
}
return policyContext.allow({
maxDailySpendingLimit: dailySpendingLimit,
currentDailySpending,
allowedTokens: allowedTokens || [],
});
},
evalAllowResultSchema: z.object({
maxDailySpendingLimit: z.number(),
currentDailySpending: z.number(),
allowedTokens: z.array(z.string()),
}),
evalDenyResultSchema: z.object({
reason: z.string(),
maxDailySpendingLimit: z.number(),
currentDailySpending: z.number(),
allowedTokens: z.array(z.string()),
}),
evaluate: async ({ abilityParams, userParams }, policyContext) => {
// Similar to precheck but with blockchain access
const { amount, tokenAddress } = abilityParams;
const { dailySpendingLimit, allowedTokens } = userParams;
// Can access on-chain data here
const { isSpendingLimitExceeded, currentDailySpending } = await checkOnChainSpendingLimit(
policyContext.delegation.delegatorPkpInfo.ethAddress,
tokenAddress,
amount,
dailySpendingLimit,
);
if (isSpendingLimitExceeded) {
return policyContext.deny({
reason: 'Spending limit exceeded',
maxDailySpendingLimit: dailySpendingLimit,
currentDailySpending,
allowedTokens: allowedTokens || [],
});
}
return policyContext.allow({
maxDailySpendingLimit: dailySpendingLimit,
currentDailySpending,
allowedTokens: allowedTokens || [],
});
},
commitParamsSchema: z.object({
spentAmount: z.number(),
tokenAddress: z.string(),
}),
commitAllowResultSchema: z.object({
updatedDailySpending: z.number(),
remainingDailyLimit: z.number(),
}),
commitDenyResultSchema: z.object({
reason: z.string(),
vincentAppId: z.number(),
spenderAddress: z.string(),
spentAmount: z.number(),
spentTokenAddress: z.string(),
}),
commit: async (params, policyContext) => {
const { spentAmount, tokenAddress } = params;
try {
const { updatedDailySpending, remainingDailyLimit } = await updateSpentAmount({
vincentAppId: policyContext.appId,
spenderAddress: policyContext.delegation.delegatorPkpInfo.ethAddress,
spentAmount,
spentTokenAddress: tokenAddress,
});
return policyContext.allow({
updatedDailySpending,
remainingDailyLimit,
});
} catch (error) {
return policyContext.deny({
reason: 'Failed to update spending limit',
vincentAppId: policyContext.appId,
spenderAddress: policyContext.delegation.delegatorPkpInfo.ethAddress,
spentAmount,
spentTokenAddress: tokenAddress,
});
}
},
});