Skip to content

Commit 1f11280

Browse files
committed
feat: add pruneExpired helper function
1 parent af6c29c commit 1f11280

File tree

3 files changed

+136
-13
lines changed

3 files changed

+136
-13
lines changed

local-tests/test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ import { testFailBatchGeneratePrivateKeysAtomic } from './tests/wrapped-keys/tes
114114
import { setLitActionsCodeToLocal } from './tests/wrapped-keys/util';
115115
import { testUseEoaSessionSigsToRequestSingleResponse } from './tests/testUseEoaSessionSigsToRequestSingleResponse';
116116
import { testPkpSessionSigsDomain } from './tests/testPkpSessionSigsDomain';
117+
import { testPruneRLI } from './tests/testPruneRLI';
117118

118119
// Use the current LIT action code to test against
119120
setLitActionsCodeToLocal();
@@ -297,6 +298,10 @@ setLitActionsCodeToLocal();
297298
},
298299
};
299300

301+
const others = {
302+
testPruneRLI,
303+
};
304+
300305
const testConfig = {
301306
tests: {
302307
// testExample,
@@ -319,6 +324,8 @@ setLitActionsCodeToLocal();
319324

320325
...relayerTests,
321326
...wrappedKeysTests,
327+
328+
...others,
322329
},
323330
devEnv,
324331
};

local-tests/tests/testPruneRLI.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { TinnyEnvironment } from 'local-tests/setup/tinny-environment';
2+
3+
/**
4+
* Test Commands:
5+
* ✅ NETWORK=datil-dev yarn test:local --filter=testPruneRLI
6+
* ✅ NETWORK=datil-test yarn test:local --filter=testPruneRLI
7+
* ✅ NETWORK=custom yarn test:local --filter=testPruneRLI
8+
*/
9+
export const testPruneRLI = async (devEnv: TinnyEnvironment) => {
10+
const alice = await devEnv.createRandomPerson();
11+
12+
const res =
13+
await alice.contractsClient.rateLimitNftContractUtils.write.pruneExpired(
14+
alice.wallet.address
15+
);
16+
17+
console.log(res);
18+
};

packages/contracts-sdk/src/lib/contracts-sdk.ts

Lines changed: 111 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,37 +48,36 @@ import * as stakingBalancesContract from '../abis/StakingBalances.sol/StakingBal
4848
// ----- autogen:imports:end -----
4949

5050
import {
51-
AUTH_METHOD_TYPE_VALUES,
5251
AUTH_METHOD_SCOPE_VALUES,
53-
METAMASK_CHAIN_INFO_BY_NETWORK,
54-
NETWORK_CONTEXT_BY_NETWORK,
55-
LIT_NETWORK_VALUES,
56-
RPC_URL_BY_NETWORK,
57-
HTTP_BY_NETWORK,
52+
AUTH_METHOD_TYPE_VALUES,
5853
CENTRALISATION_BY_NETWORK,
59-
LIT_NETWORK,
6054
HTTP,
6155
HTTPS,
56+
HTTP_BY_NETWORK,
6257
InitError,
63-
NetworkError,
64-
WrongNetworkException,
65-
ParamsMissingError,
6658
InvalidArgumentException,
59+
LIT_NETWORK,
60+
LIT_NETWORK_VALUES,
61+
METAMASK_CHAIN_INFO_BY_NETWORK,
62+
NETWORK_CONTEXT_BY_NETWORK,
63+
ParamsMissingError,
64+
RPC_URL_BY_NETWORK,
6765
TransactionError,
66+
WrongNetworkException,
6867
} from '@lit-protocol/constants';
6968
import { LogManager, Logger } from '@lit-protocol/logger';
69+
import { derivedAddresses } from '@lit-protocol/misc';
7070
import { TokenInfo } from '@lit-protocol/types';
7171
import { computeAddress } from 'ethers/lib/utils';
7272
import { IPubkeyRouter } from '../abis/PKPNFT.sol/PKPNFT';
73-
import { derivedAddresses } from '@lit-protocol/misc';
7473
import { getAuthIdByAuthMethod, stringToArrayify } from './auth-utils';
7574
import {
7675
CIDParser,
7776
IPFSHash,
7877
getBytes32FromMultihash,
7978
} from './helpers/getBytes32FromMultihash';
80-
import { calculateUTCMidnightExpiration, requestsToKilosecond } from './utils';
8179
import { ValidatorStruct } from './types';
80+
import { calculateUTCMidnightExpiration, requestsToKilosecond } from './utils';
8281

8382
// const DEFAULT_RPC = 'https://lit-protocol.calderachain.xyz/replica-http';
8483
// const DEFAULT_READ_RPC = 'https://lit-protocol.calderachain.xyz/replica-http';
@@ -748,7 +747,8 @@ export class LitContracts {
748747
): Promise<string> {
749748
let address: string = '';
750749
switch (contract) {
751-
case 'Allowlist' || 'AllowList':
750+
case 'Allowlist':
751+
case 'AllowList':
752752
address = await resolverContract['getContract'](
753753
await resolverContract['ALLOWLIST_CONTRACT'](),
754754
environment
@@ -2870,6 +2870,104 @@ https://developer.litprotocol.com/v3/sdk/wallets/auth-methods/#auth-method-scope
28702870

28712871
return tx;
28722872
},
2873+
/**
2874+
* Prune expired Capacity Credits NFT (RLI) tokens for a specified owner address.
2875+
* This function burns all expired RLI tokens owned by the target address, helping to clean up the blockchain.
2876+
* Anyone can call this function to prune expired tokens for any address.
2877+
*
2878+
* @param {string} ownerAddress - The address of the owner to prune expired tokens for.
2879+
* @returns {Promise<PruneExpiredCapacityCreditsRes>} - A promise that resolves to the pruning response with transaction details.
2880+
* @throws {Error} - If the input parameters are invalid or an error occurs during the pruning process.
2881+
*/
2882+
pruneExpired: async (
2883+
ownerAddress: string
2884+
): Promise<{
2885+
txHash: string;
2886+
}> => {
2887+
this.log('Pruning expired Capacity Credits NFTs...');
2888+
2889+
// Validate input: ownerAddress must be a valid Ethereum address
2890+
if (!ownerAddress || !ethers.utils.isAddress(ownerAddress)) {
2891+
throw new InvalidArgumentException(
2892+
{
2893+
info: {
2894+
ownerAddress,
2895+
},
2896+
},
2897+
`A valid owner address is required to prune expired tokens`
2898+
);
2899+
}
2900+
2901+
this.log(`Target owner address: ${ownerAddress}`);
2902+
2903+
try {
2904+
// Hardcoded ABI for pruneExpired function
2905+
const pruneExpiredABI = [
2906+
{
2907+
inputs: [
2908+
{
2909+
internalType: 'address',
2910+
name: 'owner',
2911+
type: 'address',
2912+
},
2913+
],
2914+
name: 'pruneExpired',
2915+
outputs: [],
2916+
stateMutability: 'nonpayable',
2917+
type: 'function',
2918+
},
2919+
];
2920+
2921+
// Create contract instance with the hardcoded ABI
2922+
const contractAddress = this.rateLimitNftContract.read.address;
2923+
const contract = new ethers.Contract(
2924+
contractAddress,
2925+
pruneExpiredABI,
2926+
this.signer
2927+
);
2928+
2929+
// Call the pruneExpired function
2930+
const res = await contract['pruneExpired'](ownerAddress);
2931+
2932+
const txHash = res.hash;
2933+
this.log(`Prune transaction submitted: ${txHash}`);
2934+
2935+
const tx = await res.wait();
2936+
this.log('Prune transaction confirmed:', tx);
2937+
2938+
// Count the burned tokens from Transfer events (transfers to zero address)
2939+
const burnEvents = tx.logs.filter((log: any) => {
2940+
try {
2941+
const parsedLog =
2942+
this.rateLimitNftContract.read.interface.parseLog(log);
2943+
return (
2944+
parsedLog.name === 'Transfer' &&
2945+
parsedLog.args['to'] === ethers.constants.AddressZero
2946+
);
2947+
} catch {
2948+
return false;
2949+
}
2950+
});
2951+
2952+
const actualTokensBurned = burnEvents.length;
2953+
this.log(`Successfully burned ${actualTokensBurned} expired tokens`);
2954+
this.log(`Gas used: ${tx.gasUsed.toString()}`);
2955+
2956+
return {
2957+
txHash,
2958+
};
2959+
} catch (e) {
2960+
throw new TransactionError(
2961+
{
2962+
info: {
2963+
ownerAddress,
2964+
},
2965+
cause: e,
2966+
},
2967+
'Pruning expired capacity credits NFTs failed'
2968+
);
2969+
}
2970+
},
28732971
},
28742972
};
28752973

0 commit comments

Comments
 (0)