diff --git a/.gitignore b/.gitignore index 7e32b04e0..2963edda1 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,6 @@ ccip-api-ref/docs-api/v1/* !ccip-api-ref/docs-api/v1/sidebar.d.ts # Canton CLI config -canton-config.json \ No newline at end of file +canton-config.json + +pnpm-lock.yaml \ No newline at end of file diff --git a/ccip-cli/src/index.ts b/ccip-cli/src/index.ts index f313f6a4f..fc0df1ae5 100755 --- a/ccip-cli/src/index.ts +++ b/ccip-cli/src/index.ts @@ -31,7 +31,7 @@ Error.stackTraceLimit = 50 // show more stack frames for better debugging // generate:nofail // `const VERSION = '${require('./package.json').version}-${require('child_process').execSync('git rev-parse --short HEAD').toString().trim()}'` -const VERSION = '1.13.1-e6a58224' +const VERSION = '1.13.1-247aa263' // generate:end const require = createRequire(import.meta.url) diff --git a/ccip-sdk/package.json b/ccip-sdk/package.json index b0c536111..40f9e852c 100644 --- a/ccip-sdk/package.json +++ b/ccip-sdk/package.json @@ -27,6 +27,14 @@ "types": "./dist/all-chains.d.ts", "default": "./dist/all-chains.js" }, + "./cct/evm": { + "types": "./dist/cct/evm/index.d.ts", + "default": "./dist/cct/evm/index.js" + }, + "./cct/solana": { + "types": "./dist/cct/solana/index.d.ts", + "default": "./dist/cct/solana/index.js" + }, "./dist/*": "./dist/*", "./src/*": "./src/*" }, @@ -68,6 +76,10 @@ "dependencies": { "@aptos-labs/ts-sdk": "^7.3.0", "@coral-xyz/anchor": "^0.29.0", + "@metaplex-foundation/mpl-token-metadata": "3.4.0", + "@metaplex-foundation/umi": "1.5.1", + "@metaplex-foundation/umi-bundle-defaults": "1.5.1", + "@metaplex-foundation/umi-web3js-adapters": "1.5.1", "@mysten/bcs": "^2.1.0", "@mysten/sui": "^2.23.2", "@noble/hashes": "^2.3.0", diff --git a/ccip-sdk/src/api/index.ts b/ccip-sdk/src/api/index.ts index f1f681062..aea5ec973 100644 --- a/ccip-sdk/src/api/index.ts +++ b/ccip-sdk/src/api/index.ts @@ -63,7 +63,7 @@ export const DEFAULT_TIMEOUT_MS = 30000 /** SDK version string for telemetry header */ // generate:nofail // `export const SDK_VERSION = '${require('./package.json').version}-${require('child_process').execSync('git rev-parse --short HEAD').toString().trim()}'` -export const SDK_VERSION = '1.13.1-e6a58224' +export const SDK_VERSION = '1.13.1-247aa263' // generate:end /** SDK telemetry header name */ diff --git a/ccip-sdk/src/cct/errors.ts b/ccip-sdk/src/cct/errors.ts new file mode 100644 index 000000000..3b9f25fe4 --- /dev/null +++ b/ccip-sdk/src/cct/errors.ts @@ -0,0 +1,233 @@ +/** + * CCT-specific error classes for write operations (validate → encode → submit). + * Shared CCIP errors (`CCIPWalletInvalidError`, etc.) live in `../errors/`. + * + * @packageDocumentation + */ + +import { type CCIPErrorOptions, CCIPError, CCIPErrorCode } from '../errors/index.ts' + +// Parameter validation + +/** + * Thrown before any RPC when operation params fail validation. Permanent. + * + * @example + * ```typescript + * try { + * await cct.setPool({ tokenAddress: 'not-an-address', poolAddress, address, wallet }) + * } catch (error) { + * if (error instanceof CCTParamsInvalidError) { + * console.log(`Invalid ${error.context.operation} param "${error.context.param}"`) + * } + * } + * ``` + */ +export class CCTParamsInvalidError extends CCIPError { + override readonly name = 'CCTParamsInvalidError' + /** Creates a params-invalid error. */ + constructor(operation: string, param: string, reason: string, options?: CCIPErrorOptions) { + super( + CCIPErrorCode.CCT_PARAMS_INVALID, + `Invalid ${operation} parameter "${param}": ${reason}`, + { + ...options, + isTransient: false, + context: { ...options?.context, operation, param, reason }, + }, + ) + } +} + +// Transaction submission + +/** + * Thrown when a CCT write fails before broadcast, the transaction reverts after mining, + * or it mines without the expected effect (e.g. a deployment that produced no contract + * address). Pre-broadcast failures (signing/RPC) may set `isTransient: true` for network + * errors; reverts and post-mining anomalies are permanent and include `context.txHash`. + * + * @example + * ```typescript + * try { + * await cct.setPool({ ...opts, wallet }) + * } catch (error) { + * if (error instanceof CCTTxFailedError) { + * console.log(`${error.context.operation} failed: ${error.context.reason}`) + * } + * } + * ``` + */ +export class CCTTxFailedError extends CCIPError { + override readonly name = 'CCTTxFailedError' + /** Creates a tx-failed error. */ + constructor(operation: string, reason: string, options?: CCIPErrorOptions) { + super(CCIPErrorCode.CCT_TX_FAILED, `${operation} failed: ${reason}`, { + ...options, + isTransient: options?.isTransient ?? false, + context: { ...options?.context, operation, reason }, + }) + } +} + +/** + * Thrown when a transaction was broadcast but not confirmed within the timeout. + * Transient — it may still mine; check `context.txHash` before resubmitting. + * + * @example + * ```typescript + * try { + * await cct.setPool({ ...opts, wallet }) + * } catch (error) { + * if (error instanceof CCTTxNotConfirmedError) { + * console.log(`Not confirmed (tx ${error.context.txHash}); retry in ${error.retryAfterMs}ms`) + * } + * } + * ``` + */ +export class CCTTxNotConfirmedError extends CCIPError { + override readonly name = 'CCTTxNotConfirmedError' + /** Creates a tx-not-confirmed error. */ + constructor(operation: string, txHash: string, options?: CCIPErrorOptions) { + super( + CCIPErrorCode.CCT_TX_NOT_CONFIRMED, + `${operation} transaction not confirmed within timeout: ${txHash}`, + { + ...options, + isTransient: true, + retryAfterMs: 5000, + context: { ...options?.context, operation, txHash }, + }, + ) + } +} + +// Contract version dispatch + +/** + * Thrown when the contract at an address is not of the expected type. + * + * @example + * ```typescript + * try { + * await cct.transferOwnership({ poolAddress, newOwner, wallet }) + * } catch (error) { + * if (error instanceof CCTContractTypeInvalidError) { + * console.log(`Expected ${error.context.expected} at ${error.context.address}, got "${error.context.actual}"`) + * } + * } + * ``` + */ +export class CCTContractTypeInvalidError extends CCIPError { + override readonly name = 'CCTContractTypeInvalidError' + /** + * Creates a contract-type-invalid error. `reason` is appended to the message and kept in + * `context`; pass it when `actual` is a recognized type rejected on its own grounds, so the + * message does not read as "wrong address". + */ + constructor( + address: string, + expected: string, + actual: string, + reason?: string, + options?: CCIPErrorOptions, + ) { + super( + CCIPErrorCode.CONTRACT_TYPE_INVALID, + `Expected a ${expected} contract at ${address}, got "${actual}"` + + (reason ? ` — ${reason}` : ''), + { + ...options, + isTransient: false, + context: { ...options?.context, address, expected, actual, ...(reason && { reason }) }, + }, + ) + } +} + +/** + * Thrown when a contract reports a version string the SDK does not recognize. Permanent. + * + * @example + * ```typescript + * try { + * await cct.transferOwnership({ poolAddress, newOwner, wallet }) + * } catch (error) { + * if (error instanceof CCTContractVersionUnsupportedError) { + * console.log(`Unsupported ${error.context.contractType} version: ${error.context.version}`) + * } + * } + * ``` + */ +export class CCTContractVersionUnsupportedError extends CCIPError { + override readonly name = 'CCTContractVersionUnsupportedError' + /** Creates a contract-version-unsupported error. */ + constructor(contractType: string, version: string, options?: CCIPErrorOptions) { + super( + CCIPErrorCode.CCT_CONTRACT_VERSION_UNSUPPORTED, + `Unsupported ${contractType} version: ${version}`, + { + ...options, + isTransient: false, + context: { ...options?.context, contractType, version }, + }, + ) + } +} + +/** + * Thrown when no implementation is registered for an operation at or below the contract's + * version (floor-match miss). Permanent for that contract version. + * + * @example + * ```typescript + * try { + * await cct.transferOwnership({ poolAddress, newOwner, wallet }) + * } catch (error) { + * if (error instanceof CCTOperationUnsupportedError) { + * console.log(`${error.context.operation} unsupported at version ${error.context.version}`) + * } + * } + * ``` + */ +export class CCTOperationUnsupportedError extends CCIPError { + override readonly name = 'CCTOperationUnsupportedError' + /** Creates an operation-unsupported error. */ + constructor(operation: string, version: string, options?: CCIPErrorOptions) { + super( + CCIPErrorCode.CCT_OPERATION_UNSUPPORTED, + `${operation} is not supported at contract version ${version}`, + { + ...options, + isTransient: false, + context: { ...options?.context, operation, version }, + }, + ) + } +} + +/** + * Thrown when CCT account data cannot be decoded. + * + * @example + * ```typescript + * try { + * await cct.getTokenPoolState({ tokenAddress: mint, poolType: 'burn-mint' }) + * } catch (error) { + * if (error instanceof CCTDataDecodeError) { + * console.log(error.message) + * } + * } + * ``` + */ +export class CCTDataDecodeError extends CCIPError { + override readonly name = 'CCTDataDecodeError' + /** Creates a CCT data decode error. */ + constructor(account: string, options?: CCIPErrorOptions) { + super(CCIPErrorCode.CCT_DATA_DECODE_FAILED, `Unable to decode CCT data at ${account}`, { + ...options, + isTransient: false, + context: { ...options?.context, account }, + }) + } +} diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_0/burn-mint-token-pool-and-proxy.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_0/burn-mint-token-pool-and-proxy.ts new file mode 100644 index 000000000..e04b953f9 --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_0/burn-mint-token-pool-and-proxy.ts @@ -0,0 +1,1055 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v1_5_0/burn_mint_token_pool_and_proxy.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + inputs: [ + { + internalType: 'contract IBurnMintERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'address[]', + name: 'allowlist', + type: 'address[]', + }, + { internalType: 'address', name: 'rmnProxy', type: 'address' }, + { internalType: 'address', name: 'router', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [ + { internalType: 'uint256', name: 'capacity', type: 'uint256' }, + { internalType: 'uint256', name: 'requested', type: 'uint256' }, + ], + name: 'AggregateValueMaxCapacityExceeded', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'minWaitInSeconds', + type: 'uint256', + }, + { internalType: 'uint256', name: 'available', type: 'uint256' }, + ], + name: 'AggregateValueRateLimitReached', + type: 'error', + }, + { inputs: [], name: 'AllowListNotEnabled', type: 'error' }, + { inputs: [], name: 'BucketOverfilled', type: 'error' }, + { + inputs: [{ internalType: 'address', name: 'caller', type: 'address' }], + name: 'CallerIsNotARampOnRouter', + type: 'error', + }, + { + inputs: [{ internalType: 'uint64', name: 'chainSelector', type: 'uint64' }], + name: 'ChainAlreadyExists', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'ChainNotAllowed', + type: 'error', + }, + { inputs: [], name: 'CursedByRMN', type: 'error' }, + { + inputs: [ + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'struct RateLimiter.Config', + name: 'config', + type: 'tuple', + }, + ], + name: 'DisabledNonZeroRateLimit', + type: 'error', + }, + { + inputs: [ + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'struct RateLimiter.Config', + name: 'rateLimiterConfig', + type: 'tuple', + }, + ], + name: 'InvalidRateLimitRate', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'sourcePoolAddress', + type: 'bytes', + }, + ], + name: 'InvalidSourcePoolAddress', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'token', type: 'address' }], + name: 'InvalidToken', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'NonExistentChain', + type: 'error', + }, + { inputs: [], name: 'RateLimitMustBeDisabled', type: 'error' }, + { + inputs: [{ internalType: 'address', name: 'sender', type: 'address' }], + name: 'SenderNotAllowed', + type: 'error', + }, + { + inputs: [ + { internalType: 'uint256', name: 'capacity', type: 'uint256' }, + { internalType: 'uint256', name: 'requested', type: 'uint256' }, + { + internalType: 'address', + name: 'tokenAddress', + type: 'address', + }, + ], + name: 'TokenMaxCapacityExceeded', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'minWaitInSeconds', + type: 'uint256', + }, + { internalType: 'uint256', name: 'available', type: 'uint256' }, + { + internalType: 'address', + name: 'tokenAddress', + type: 'address', + }, + ], + name: 'TokenRateLimitReached', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'caller', type: 'address' }], + name: 'Unauthorized', + type: 'error', + }, + { inputs: [], name: 'ZeroAddressNotAllowed', type: 'error' }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'sender', + type: 'address', + }, + ], + name: 'AllowListAdd', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'sender', + type: 'address', + }, + ], + name: 'AllowListRemove', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Burned', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + indexed: false, + internalType: 'bytes', + name: 'remoteToken', + type: 'bytes', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'struct RateLimiter.Config', + name: 'outboundRateLimiterConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'struct RateLimiter.Config', + name: 'inboundRateLimiterConfig', + type: 'tuple', + }, + ], + name: 'ChainAdded', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'struct RateLimiter.Config', + name: 'outboundRateLimiterConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'struct RateLimiter.Config', + name: 'inboundRateLimiterConfig', + type: 'tuple', + }, + ], + name: 'ChainConfigured', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'ChainRemoved', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'struct RateLimiter.Config', + name: 'config', + type: 'tuple', + }, + ], + name: 'ConfigChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'contract IPoolPriorTo1_5', + name: 'oldPool', + type: 'address', + }, + { + indexed: false, + internalType: 'contract IPoolPriorTo1_5', + name: 'newPool', + type: 'address', + }, + ], + name: 'LegacyPoolChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Locked', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Minted', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'OwnershipTransferRequested', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Released', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + indexed: false, + internalType: 'bytes', + name: 'previousPoolAddress', + type: 'bytes', + }, + { + indexed: false, + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'RemotePoolSet', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'oldRouter', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'newRouter', + type: 'address', + }, + ], + name: 'RouterUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'tokens', + type: 'uint256', + }, + ], + name: 'TokensConsumed', + type: 'event', + }, + { + inputs: [], + name: 'acceptOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address[]', name: 'removes', type: 'address[]' }, + { internalType: 'address[]', name: 'adds', type: 'address[]' }, + ], + name: 'applyAllowListUpdates', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { internalType: 'bool', name: 'allowed', type: 'bool' }, + { + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'remoteTokenAddress', + type: 'bytes', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'rate', + type: 'uint128', + }, + ], + internalType: 'struct RateLimiter.Config', + name: 'outboundRateLimiterConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'rate', + type: 'uint128', + }, + ], + internalType: 'struct RateLimiter.Config', + name: 'inboundRateLimiterConfig', + type: 'tuple', + }, + ], + internalType: 'struct TokenPool.ChainUpdate[]', + name: 'chains', + type: 'tuple[]', + }, + ], + name: 'applyChainUpdates', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'getAllowList', + outputs: [{ internalType: 'address[]', name: '', type: 'address[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getAllowListEnabled', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getCurrentInboundRateLimiterState', + outputs: [ + { + components: [ + { internalType: 'uint128', name: 'tokens', type: 'uint128' }, + { + internalType: 'uint32', + name: 'lastUpdated', + type: 'uint32', + }, + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'struct RateLimiter.TokenBucket', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getCurrentOutboundRateLimiterState', + outputs: [ + { + components: [ + { internalType: 'uint128', name: 'tokens', type: 'uint128' }, + { + internalType: 'uint32', + name: 'lastUpdated', + type: 'uint32', + }, + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'struct RateLimiter.TokenBucket', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint64', name: '', type: 'uint64' }], + name: 'getOnRamp', + outputs: [ + { + internalType: 'address', + name: 'onRampAddress', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPreviousPool', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getRateLimitAdmin', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getRemotePool', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getRemoteToken', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getRmnProxy', + outputs: [{ internalType: 'address', name: 'rmnProxy', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getRouter', + outputs: [{ internalType: 'address', name: 'router', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getSupportedChains', + outputs: [{ internalType: 'uint64[]', name: '', type: 'uint64[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getToken', + outputs: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'sourceChainSelector', + type: 'uint64', + }, + { internalType: 'address', name: 'offRamp', type: 'address' }, + ], + name: 'isOffRamp', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'isSupportedChain', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'token', type: 'address' }], + name: 'isSupportedToken', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'bytes', name: 'receiver', type: 'bytes' }, + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'address', + name: 'originalSender', + type: 'address', + }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { + internalType: 'address', + name: 'localToken', + type: 'address', + }, + ], + internalType: 'struct Pool.LockOrBurnInV1', + name: 'lockOrBurnIn', + type: 'tuple', + }, + ], + name: 'lockOrBurn', + outputs: [ + { + components: [ + { + internalType: 'bytes', + name: 'destTokenAddress', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'destPoolData', + type: 'bytes', + }, + ], + internalType: 'struct Pool.LockOrBurnOutV1', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'bytes', + name: 'originalSender', + type: 'bytes', + }, + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'address', + name: 'receiver', + type: 'address', + }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { + internalType: 'address', + name: 'localToken', + type: 'address', + }, + { + internalType: 'bytes', + name: 'sourcePoolAddress', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'sourcePoolData', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'offchainTokenData', + type: 'bytes', + }, + ], + internalType: 'struct Pool.ReleaseOrMintInV1', + name: 'releaseOrMintIn', + type: 'tuple', + }, + ], + name: 'releaseOrMint', + outputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'destinationAmount', + type: 'uint256', + }, + ], + internalType: 'struct Pool.ReleaseOrMintOutV1', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'struct RateLimiter.Config', + name: 'outboundConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'struct RateLimiter.Config', + name: 'inboundConfig', + type: 'tuple', + }, + ], + name: 'setChainRateLimiterConfig', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IPoolPriorTo1_5', + name: 'prevPool', + type: 'address', + }, + ], + name: 'setPreviousPool', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'rateLimitAdmin', + type: 'address', + }, + ], + name: 'setRateLimitAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'setRemotePool', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'newRouter', type: 'address' }], + name: 'setRouter', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'to', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'typeAndVersion', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_0/lock-release-token-pool-and-proxy.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_0/lock-release-token-pool-and-proxy.ts new file mode 100644 index 000000000..4400c01b2 --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_0/lock-release-token-pool-and-proxy.ts @@ -0,0 +1,1141 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v1_5_0/lock_release_token_pool_and_proxy.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + inputs: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'address[]', + name: 'allowlist', + type: 'address[]', + }, + { internalType: 'address', name: 'rmnProxy', type: 'address' }, + { internalType: 'bool', name: 'acceptLiquidity', type: 'bool' }, + { internalType: 'address', name: 'router', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [ + { internalType: 'uint256', name: 'capacity', type: 'uint256' }, + { internalType: 'uint256', name: 'requested', type: 'uint256' }, + ], + name: 'AggregateValueMaxCapacityExceeded', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'minWaitInSeconds', + type: 'uint256', + }, + { internalType: 'uint256', name: 'available', type: 'uint256' }, + ], + name: 'AggregateValueRateLimitReached', + type: 'error', + }, + { inputs: [], name: 'AllowListNotEnabled', type: 'error' }, + { inputs: [], name: 'BucketOverfilled', type: 'error' }, + { + inputs: [{ internalType: 'address', name: 'caller', type: 'address' }], + name: 'CallerIsNotARampOnRouter', + type: 'error', + }, + { + inputs: [{ internalType: 'uint64', name: 'chainSelector', type: 'uint64' }], + name: 'ChainAlreadyExists', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'ChainNotAllowed', + type: 'error', + }, + { inputs: [], name: 'CursedByRMN', type: 'error' }, + { + inputs: [ + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'struct RateLimiter.Config', + name: 'config', + type: 'tuple', + }, + ], + name: 'DisabledNonZeroRateLimit', + type: 'error', + }, + { inputs: [], name: 'InsufficientLiquidity', type: 'error' }, + { + inputs: [ + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'struct RateLimiter.Config', + name: 'rateLimiterConfig', + type: 'tuple', + }, + ], + name: 'InvalidRateLimitRate', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'sourcePoolAddress', + type: 'bytes', + }, + ], + name: 'InvalidSourcePoolAddress', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'token', type: 'address' }], + name: 'InvalidToken', + type: 'error', + }, + { inputs: [], name: 'LiquidityNotAccepted', type: 'error' }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'NonExistentChain', + type: 'error', + }, + { inputs: [], name: 'RateLimitMustBeDisabled', type: 'error' }, + { + inputs: [{ internalType: 'address', name: 'sender', type: 'address' }], + name: 'SenderNotAllowed', + type: 'error', + }, + { + inputs: [ + { internalType: 'uint256', name: 'capacity', type: 'uint256' }, + { internalType: 'uint256', name: 'requested', type: 'uint256' }, + { + internalType: 'address', + name: 'tokenAddress', + type: 'address', + }, + ], + name: 'TokenMaxCapacityExceeded', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'minWaitInSeconds', + type: 'uint256', + }, + { internalType: 'uint256', name: 'available', type: 'uint256' }, + { + internalType: 'address', + name: 'tokenAddress', + type: 'address', + }, + ], + name: 'TokenRateLimitReached', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'caller', type: 'address' }], + name: 'Unauthorized', + type: 'error', + }, + { inputs: [], name: 'ZeroAddressNotAllowed', type: 'error' }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'sender', + type: 'address', + }, + ], + name: 'AllowListAdd', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'sender', + type: 'address', + }, + ], + name: 'AllowListRemove', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Burned', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + indexed: false, + internalType: 'bytes', + name: 'remoteToken', + type: 'bytes', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'struct RateLimiter.Config', + name: 'outboundRateLimiterConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'struct RateLimiter.Config', + name: 'inboundRateLimiterConfig', + type: 'tuple', + }, + ], + name: 'ChainAdded', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'struct RateLimiter.Config', + name: 'outboundRateLimiterConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'struct RateLimiter.Config', + name: 'inboundRateLimiterConfig', + type: 'tuple', + }, + ], + name: 'ChainConfigured', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'ChainRemoved', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'struct RateLimiter.Config', + name: 'config', + type: 'tuple', + }, + ], + name: 'ConfigChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'contract IPoolPriorTo1_5', + name: 'oldPool', + type: 'address', + }, + { + indexed: false, + internalType: 'contract IPoolPriorTo1_5', + name: 'newPool', + type: 'address', + }, + ], + name: 'LegacyPoolChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'provider', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'LiquidityAdded', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'provider', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'LiquidityRemoved', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Locked', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Minted', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'OwnershipTransferRequested', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Released', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + indexed: false, + internalType: 'bytes', + name: 'previousPoolAddress', + type: 'bytes', + }, + { + indexed: false, + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'RemotePoolSet', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'oldRouter', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'newRouter', + type: 'address', + }, + ], + name: 'RouterUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'tokens', + type: 'uint256', + }, + ], + name: 'TokensConsumed', + type: 'event', + }, + { + inputs: [], + name: 'acceptOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address[]', name: 'removes', type: 'address[]' }, + { internalType: 'address[]', name: 'adds', type: 'address[]' }, + ], + name: 'applyAllowListUpdates', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { internalType: 'bool', name: 'allowed', type: 'bool' }, + { + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'remoteTokenAddress', + type: 'bytes', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'rate', + type: 'uint128', + }, + ], + internalType: 'struct RateLimiter.Config', + name: 'outboundRateLimiterConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'rate', + type: 'uint128', + }, + ], + internalType: 'struct RateLimiter.Config', + name: 'inboundRateLimiterConfig', + type: 'tuple', + }, + ], + internalType: 'struct TokenPool.ChainUpdate[]', + name: 'chains', + type: 'tuple[]', + }, + ], + name: 'applyChainUpdates', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'canAcceptLiquidity', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getAllowList', + outputs: [{ internalType: 'address[]', name: '', type: 'address[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getAllowListEnabled', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getCurrentInboundRateLimiterState', + outputs: [ + { + components: [ + { internalType: 'uint128', name: 'tokens', type: 'uint128' }, + { + internalType: 'uint32', + name: 'lastUpdated', + type: 'uint32', + }, + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'struct RateLimiter.TokenBucket', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getCurrentOutboundRateLimiterState', + outputs: [ + { + components: [ + { internalType: 'uint128', name: 'tokens', type: 'uint128' }, + { + internalType: 'uint32', + name: 'lastUpdated', + type: 'uint32', + }, + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'struct RateLimiter.TokenBucket', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint64', name: '', type: 'uint64' }], + name: 'getOnRamp', + outputs: [ + { + internalType: 'address', + name: 'onRampAddress', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPreviousPool', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getRateLimitAdmin', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getRebalancer', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getRemotePool', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getRemoteToken', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getRmnProxy', + outputs: [{ internalType: 'address', name: 'rmnProxy', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getRouter', + outputs: [{ internalType: 'address', name: 'router', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getSupportedChains', + outputs: [{ internalType: 'uint64[]', name: '', type: 'uint64[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getToken', + outputs: [ + { + internalType: 'contract IERC20', + name: 'token', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'sourceChainSelector', + type: 'uint64', + }, + { internalType: 'address', name: 'offRamp', type: 'address' }, + ], + name: 'isOffRamp', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'isSupportedChain', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'token', type: 'address' }], + name: 'isSupportedToken', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'bytes', name: 'receiver', type: 'bytes' }, + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'address', + name: 'originalSender', + type: 'address', + }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { + internalType: 'address', + name: 'localToken', + type: 'address', + }, + ], + internalType: 'struct Pool.LockOrBurnInV1', + name: 'lockOrBurnIn', + type: 'tuple', + }, + ], + name: 'lockOrBurn', + outputs: [ + { + components: [ + { + internalType: 'bytes', + name: 'destTokenAddress', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'destPoolData', + type: 'bytes', + }, + ], + internalType: 'struct Pool.LockOrBurnOutV1', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }], + name: 'provideLiquidity', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'bytes', + name: 'originalSender', + type: 'bytes', + }, + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'address', + name: 'receiver', + type: 'address', + }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { + internalType: 'address', + name: 'localToken', + type: 'address', + }, + { + internalType: 'bytes', + name: 'sourcePoolAddress', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'sourcePoolData', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'offchainTokenData', + type: 'bytes', + }, + ], + internalType: 'struct Pool.ReleaseOrMintInV1', + name: 'releaseOrMintIn', + type: 'tuple', + }, + ], + name: 'releaseOrMint', + outputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'destinationAmount', + type: 'uint256', + }, + ], + internalType: 'struct Pool.ReleaseOrMintOutV1', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'struct RateLimiter.Config', + name: 'outboundConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'struct RateLimiter.Config', + name: 'inboundConfig', + type: 'tuple', + }, + ], + name: 'setChainRateLimiterConfig', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IPoolPriorTo1_5', + name: 'prevPool', + type: 'address', + }, + ], + name: 'setPreviousPool', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'rateLimitAdmin', + type: 'address', + }, + ], + name: 'setRateLimitAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'rebalancer', type: 'address' }], + name: 'setRebalancer', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'setRemotePool', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'newRouter', type: 'address' }], + name: 'setRouter', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'transferLiquidity', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'to', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'typeAndVersion', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }], + name: 'withdrawLiquidity', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_0/registry-module-owner-custom.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_0/registry-module-owner-custom.ts new file mode 100644 index 000000000..33745a3be --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_0/registry-module-owner-custom.ts @@ -0,0 +1,68 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v1_5_0/registry_module_owner_custom.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + inputs: [ + { + internalType: 'address', + name: 'tokenAdminRegistry', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { inputs: [], name: 'AddressZero', type: 'error' }, + { + inputs: [ + { internalType: 'address', name: 'admin', type: 'address' }, + { internalType: 'address', name: 'token', type: 'address' }, + ], + name: 'CanOnlySelfRegister', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'token', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'administrator', + type: 'address', + }, + ], + name: 'AdministratorRegistered', + type: 'event', + }, + { + inputs: [{ internalType: 'address', name: 'token', type: 'address' }], + name: 'registerAdminViaGetCCIPAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'token', type: 'address' }], + name: 'registerAdminViaOwner', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'typeAndVersion', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_0/token-admin-registry.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_0/token-admin-registry.ts new file mode 100644 index 000000000..1e2ddcce9 --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_0/token-admin-registry.ts @@ -0,0 +1,335 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v1_5_0/token_admin_registry.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + type: 'function', + name: 'acceptAdminRole', + inputs: [{ name: 'localToken', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'acceptOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'addRegistryModule', + inputs: [{ name: 'module', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'getAllConfiguredTokens', + inputs: [ + { name: 'startIndex', type: 'uint64', internalType: 'uint64' }, + { name: 'maxCount', type: 'uint64', internalType: 'uint64' }, + ], + outputs: [{ name: 'tokens', type: 'address[]', internalType: 'address[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getPool', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getPools', + inputs: [{ name: 'tokens', type: 'address[]', internalType: 'address[]' }], + outputs: [{ name: '', type: 'address[]', internalType: 'address[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getTokenConfig', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct TokenAdminRegistry.TokenConfig', + components: [ + { + name: 'administrator', + type: 'address', + internalType: 'address', + }, + { + name: 'pendingAdministrator', + type: 'address', + internalType: 'address', + }, + { + name: 'tokenPool', + type: 'address', + internalType: 'address', + }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isAdministrator', + inputs: [ + { name: 'localToken', type: 'address', internalType: 'address' }, + { + name: 'administrator', + type: 'address', + internalType: 'address', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isRegistryModule', + inputs: [{ name: 'module', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'proposeAdministrator', + inputs: [ + { name: 'localToken', type: 'address', internalType: 'address' }, + { + name: 'administrator', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'removeRegistryModule', + inputs: [{ name: 'module', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setPool', + inputs: [ + { name: 'localToken', type: 'address', internalType: 'address' }, + { name: 'pool', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'transferAdminRole', + inputs: [ + { name: 'localToken', type: 'address', internalType: 'address' }, + { name: 'newAdmin', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [{ name: 'to', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'typeAndVersion', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'view', + }, + { + type: 'event', + name: 'AdministratorTransferRequested', + inputs: [ + { + name: 'token', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'currentAdmin', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'newAdmin', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'AdministratorTransferred', + inputs: [ + { + name: 'token', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'newAdmin', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferRequested', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'PoolSet', + inputs: [ + { + name: 'token', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'previousPool', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'newPool', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RegistryModuleAdded', + inputs: [ + { + name: 'module', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RegistryModuleRemoved', + inputs: [ + { + name: 'module', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'error', + name: 'AlreadyRegistered', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'CannotTransferToSelf', inputs: [] }, + { + type: 'error', + name: 'InvalidTokenPoolToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'MustBeProposedOwner', inputs: [] }, + { + type: 'error', + name: 'OnlyAdministrator', + inputs: [ + { name: 'sender', type: 'address', internalType: 'address' }, + { name: 'token', type: 'address', internalType: 'address' }, + ], + }, + { type: 'error', name: 'OnlyCallableByOwner', inputs: [] }, + { + type: 'error', + name: 'OnlyPendingAdministrator', + inputs: [ + { name: 'sender', type: 'address', internalType: 'address' }, + { name: 'token', type: 'address', internalType: 'address' }, + ], + }, + { + type: 'error', + name: 'OnlyRegistryModuleOrOwner', + inputs: [{ name: 'sender', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'OwnerCannotBeZero', inputs: [] }, + { type: 'error', name: 'ZeroAddress', inputs: [] }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_1/burn-mint-token-pool.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_1/burn-mint-token-pool.ts new file mode 100644 index 000000000..f9be69bb3 --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_1/burn-mint-token-pool.ts @@ -0,0 +1,1171 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v1_5_1/burn_mint_token_pool.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + type: 'constructor', + inputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IBurnMintERC20', + }, + { + name: 'localTokenDecimals', + type: 'uint8', + internalType: 'uint8', + }, + { + name: 'allowlist', + type: 'address[]', + internalType: 'address[]', + }, + { name: 'rmnProxy', type: 'address', internalType: 'address' }, + { name: 'router', type: 'address', internalType: 'address' }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'acceptOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'addRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyAllowListUpdates', + inputs: [ + { name: 'removes', type: 'address[]', internalType: 'address[]' }, + { name: 'adds', type: 'address[]', internalType: 'address[]' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyChainUpdates', + inputs: [ + { + name: 'remoteChainSelectorsToRemove', + type: 'uint64[]', + internalType: 'uint64[]', + }, + { + name: 'chainsToAdd', + type: 'tuple[]', + internalType: 'struct TokenPool.ChainUpdate[]', + components: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddresses', + type: 'bytes[]', + internalType: 'bytes[]', + }, + { + name: 'remoteTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'getAllowList', + inputs: [], + outputs: [{ name: '', type: 'address[]', internalType: 'address[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getAllowListEnabled', + inputs: [], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCurrentInboundRateLimiterState', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCurrentOutboundRateLimiterState', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRateLimitAdmin', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemotePools', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes[]', internalType: 'bytes[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemoteToken', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes', internalType: 'bytes' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRmnProxy', + inputs: [], + outputs: [{ name: 'rmnProxy', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRouter', + inputs: [], + outputs: [{ name: 'router', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getSupportedChains', + inputs: [], + outputs: [{ name: '', type: 'uint64[]', internalType: 'uint64[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getToken', + inputs: [], + outputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IERC20', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getTokenDecimals', + inputs: [], + outputs: [{ name: 'decimals', type: 'uint8', internalType: 'uint8' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'lockOrBurn', + inputs: [ + { + name: 'lockOrBurnIn', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnInV1', + components: [ + { name: 'receiver', type: 'bytes', internalType: 'bytes' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'originalSender', + type: 'address', + internalType: 'address', + }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + ], + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnOutV1', + components: [ + { + name: 'destTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'destPoolData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'releaseOrMint', + inputs: [ + { + name: 'releaseOrMintIn', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintInV1', + components: [ + { + name: 'originalSender', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'receiver', + type: 'address', + internalType: 'address', + }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'sourcePoolData', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'offchainTokenData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintOutV1', + components: [ + { + name: 'destinationAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'removeRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setChainRateLimiterConfig', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'outboundConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setChainRateLimiterConfigs', + inputs: [ + { + name: 'remoteChainSelectors', + type: 'uint64[]', + internalType: 'uint64[]', + }, + { + name: 'outboundConfigs', + type: 'tuple[]', + internalType: 'struct RateLimiter.Config[]', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundConfigs', + type: 'tuple[]', + internalType: 'struct RateLimiter.Config[]', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setRateLimitAdmin', + inputs: [ + { + name: 'rateLimitAdmin', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setRouter', + inputs: [{ name: 'newRouter', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'supportsInterface', + inputs: [{ name: 'interfaceId', type: 'bytes4', internalType: 'bytes4' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [{ name: 'to', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'typeAndVersion', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'view', + }, + { + type: 'event', + name: 'AllowListAdd', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'AllowListRemove', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Burned', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + { + name: 'remoteToken', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainConfigured', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ConfigChanged', + inputs: [ + { + name: 'config', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Locked', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Minted', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'recipient', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferRequested', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RateLimitAdminSet', + inputs: [ + { + name: 'rateLimitAdmin', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Released', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'recipient', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RouterUpdated', + inputs: [ + { + name: 'oldRouter', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'newRouter', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'TokensConsumed', + inputs: [ + { + name: 'tokens', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'error', + name: 'AggregateValueMaxCapacityExceeded', + inputs: [ + { name: 'capacity', type: 'uint256', internalType: 'uint256' }, + { name: 'requested', type: 'uint256', internalType: 'uint256' }, + ], + }, + { + type: 'error', + name: 'AggregateValueRateLimitReached', + inputs: [ + { + name: 'minWaitInSeconds', + type: 'uint256', + internalType: 'uint256', + }, + { name: 'available', type: 'uint256', internalType: 'uint256' }, + ], + }, + { type: 'error', name: 'AllowListNotEnabled', inputs: [] }, + { type: 'error', name: 'BucketOverfilled', inputs: [] }, + { + type: 'error', + name: 'CallerIsNotARampOnRouter', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'CannotTransferToSelf', inputs: [] }, + { + type: 'error', + name: 'ChainAlreadyExists', + inputs: [{ name: 'chainSelector', type: 'uint64', internalType: 'uint64' }], + }, + { + type: 'error', + name: 'ChainNotAllowed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'CursedByRMN', inputs: [] }, + { + type: 'error', + name: 'DisabledNonZeroRateLimit', + inputs: [ + { + name: 'config', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { + type: 'error', + name: 'InvalidDecimalArgs', + inputs: [ + { name: 'expected', type: 'uint8', internalType: 'uint8' }, + { name: 'actual', type: 'uint8', internalType: 'uint8' }, + ], + }, + { + type: 'error', + name: 'InvalidRateLimitRate', + inputs: [ + { + name: 'rateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { + type: 'error', + name: 'InvalidRemoteChainDecimals', + inputs: [{ name: 'sourcePoolData', type: 'bytes', internalType: 'bytes' }], + }, + { + type: 'error', + name: 'InvalidRemotePoolForChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidSourcePoolAddress', + inputs: [ + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'MismatchedArrayLengths', inputs: [] }, + { type: 'error', name: 'MustBeProposedOwner', inputs: [] }, + { + type: 'error', + name: 'NonExistentChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'OnlyCallableByOwner', inputs: [] }, + { + type: 'error', + name: 'OverflowDetected', + inputs: [ + { name: 'remoteDecimals', type: 'uint8', internalType: 'uint8' }, + { name: 'localDecimals', type: 'uint8', internalType: 'uint8' }, + { + name: 'remoteAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { type: 'error', name: 'OwnerCannotBeZero', inputs: [] }, + { + type: 'error', + name: 'PoolAlreadyAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { type: 'error', name: 'RateLimitMustBeDisabled', inputs: [] }, + { + type: 'error', + name: 'SenderNotAllowed', + inputs: [{ name: 'sender', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'TokenMaxCapacityExceeded', + inputs: [ + { name: 'capacity', type: 'uint256', internalType: 'uint256' }, + { name: 'requested', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'TokenRateLimitReached', + inputs: [ + { + name: 'minWaitInSeconds', + type: 'uint256', + internalType: 'uint256', + }, + { name: 'available', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'Unauthorized', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'ZeroAddressNotAllowed', inputs: [] }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_1/factory-burn-mint-erc20.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_1/factory-burn-mint-erc20.ts new file mode 100644 index 000000000..0dbf68f73 --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_1/factory-burn-mint-erc20.ts @@ -0,0 +1,483 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v1_5_1/factory_burn_mint_erc20.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + type: 'constructor', + inputs: [ + { name: 'name', type: 'string', internalType: 'string' }, + { name: 'symbol', type: 'string', internalType: 'string' }, + { name: 'decimals_', type: 'uint8', internalType: 'uint8' }, + { name: 'maxSupply_', type: 'uint256', internalType: 'uint256' }, + { name: 'preMint', type: 'uint256', internalType: 'uint256' }, + { name: 'newOwner', type: 'address', internalType: 'address' }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'acceptOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'allowance', + inputs: [ + { name: 'owner', type: 'address', internalType: 'address' }, + { name: 'spender', type: 'address', internalType: 'address' }, + ], + outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'approve', + inputs: [ + { name: 'spender', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'balanceOf', + inputs: [{ name: 'account', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'burn', + inputs: [{ name: 'amount', type: 'uint256', internalType: 'uint256' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'burn', + inputs: [ + { name: 'account', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'burnFrom', + inputs: [ + { name: 'account', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'decimals', + inputs: [], + outputs: [{ name: '', type: 'uint8', internalType: 'uint8' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'decreaseAllowance', + inputs: [ + { name: 'spender', type: 'address', internalType: 'address' }, + { + name: 'subtractedValue', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'decreaseApproval', + inputs: [ + { name: 'spender', type: 'address', internalType: 'address' }, + { + name: 'subtractedValue', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [{ name: 'success', type: 'bool', internalType: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'getBurners', + inputs: [], + outputs: [{ name: '', type: 'address[]', internalType: 'address[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCCIPAdmin', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getMinters', + inputs: [], + outputs: [{ name: '', type: 'address[]', internalType: 'address[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'grantBurnRole', + inputs: [{ name: 'burner', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'grantMintAndBurnRoles', + inputs: [ + { + name: 'burnAndMinter', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'grantMintRole', + inputs: [{ name: 'minter', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'increaseAllowance', + inputs: [ + { name: 'spender', type: 'address', internalType: 'address' }, + { name: 'addedValue', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'increaseApproval', + inputs: [ + { name: 'spender', type: 'address', internalType: 'address' }, + { name: 'addedValue', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'isBurner', + inputs: [{ name: 'burner', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isMinter', + inputs: [{ name: 'minter', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'maxSupply', + inputs: [], + outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'mint', + inputs: [ + { name: 'account', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'name', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'revokeBurnRole', + inputs: [{ name: 'burner', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'revokeMintRole', + inputs: [{ name: 'minter', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setCCIPAdmin', + inputs: [{ name: 'newAdmin', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'supportsInterface', + inputs: [{ name: 'interfaceId', type: 'bytes4', internalType: 'bytes4' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'symbol', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'totalSupply', + inputs: [], + outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'transfer', + inputs: [ + { name: 'to', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'transferFrom', + inputs: [ + { name: 'from', type: 'address', internalType: 'address' }, + { name: 'to', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [{ name: 'to', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'event', + name: 'Approval', + inputs: [ + { + name: 'owner', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'spender', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'BurnAccessGranted', + inputs: [ + { + name: 'burner', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'BurnAccessRevoked', + inputs: [ + { + name: 'burner', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'CCIPAdminTransferred', + inputs: [ + { + name: 'previousAdmin', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'newAdmin', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'MintAccessGranted', + inputs: [ + { + name: 'minter', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'MintAccessRevoked', + inputs: [ + { + name: 'minter', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferRequested', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Transfer', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { type: 'error', name: 'CannotTransferToSelf', inputs: [] }, + { + type: 'error', + name: 'MaxSupplyExceeded', + inputs: [ + { + name: 'supplyAfterMint', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { type: 'error', name: 'MustBeProposedOwner', inputs: [] }, + { type: 'error', name: 'OnlyCallableByOwner', inputs: [] }, + { type: 'error', name: 'OwnerCannotBeZero', inputs: [] }, + { + type: 'error', + name: 'SenderNotBurner', + inputs: [{ name: 'sender', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'SenderNotMinter', + inputs: [{ name: 'sender', type: 'address', internalType: 'address' }], + }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_1/lock-release-token-pool.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_1/lock-release-token-pool.ts new file mode 100644 index 000000000..9b072f870 --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V1_5_1/lock-release-token-pool.ts @@ -0,0 +1,1276 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v1_5_1/lock_release_token_pool.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + type: 'constructor', + inputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IERC20', + }, + { + name: 'localTokenDecimals', + type: 'uint8', + internalType: 'uint8', + }, + { + name: 'allowlist', + type: 'address[]', + internalType: 'address[]', + }, + { name: 'rmnProxy', type: 'address', internalType: 'address' }, + { name: 'acceptLiquidity', type: 'bool', internalType: 'bool' }, + { name: 'router', type: 'address', internalType: 'address' }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'acceptOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'addRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyAllowListUpdates', + inputs: [ + { name: 'removes', type: 'address[]', internalType: 'address[]' }, + { name: 'adds', type: 'address[]', internalType: 'address[]' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyChainUpdates', + inputs: [ + { + name: 'remoteChainSelectorsToRemove', + type: 'uint64[]', + internalType: 'uint64[]', + }, + { + name: 'chainsToAdd', + type: 'tuple[]', + internalType: 'struct TokenPool.ChainUpdate[]', + components: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddresses', + type: 'bytes[]', + internalType: 'bytes[]', + }, + { + name: 'remoteTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'canAcceptLiquidity', + inputs: [], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getAllowList', + inputs: [], + outputs: [{ name: '', type: 'address[]', internalType: 'address[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getAllowListEnabled', + inputs: [], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCurrentInboundRateLimiterState', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCurrentOutboundRateLimiterState', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRateLimitAdmin', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRebalancer', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemotePools', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes[]', internalType: 'bytes[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemoteToken', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes', internalType: 'bytes' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRmnProxy', + inputs: [], + outputs: [{ name: 'rmnProxy', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRouter', + inputs: [], + outputs: [{ name: 'router', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getSupportedChains', + inputs: [], + outputs: [{ name: '', type: 'uint64[]', internalType: 'uint64[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getToken', + inputs: [], + outputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IERC20', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getTokenDecimals', + inputs: [], + outputs: [{ name: 'decimals', type: 'uint8', internalType: 'uint8' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'lockOrBurn', + inputs: [ + { + name: 'lockOrBurnIn', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnInV1', + components: [ + { name: 'receiver', type: 'bytes', internalType: 'bytes' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'originalSender', + type: 'address', + internalType: 'address', + }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + ], + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnOutV1', + components: [ + { + name: 'destTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'destPoolData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'provideLiquidity', + inputs: [{ name: 'amount', type: 'uint256', internalType: 'uint256' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'releaseOrMint', + inputs: [ + { + name: 'releaseOrMintIn', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintInV1', + components: [ + { + name: 'originalSender', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'receiver', + type: 'address', + internalType: 'address', + }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'sourcePoolData', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'offchainTokenData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintOutV1', + components: [ + { + name: 'destinationAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'removeRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setChainRateLimiterConfig', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'outboundConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setChainRateLimiterConfigs', + inputs: [ + { + name: 'remoteChainSelectors', + type: 'uint64[]', + internalType: 'uint64[]', + }, + { + name: 'outboundConfigs', + type: 'tuple[]', + internalType: 'struct RateLimiter.Config[]', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundConfigs', + type: 'tuple[]', + internalType: 'struct RateLimiter.Config[]', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setRateLimitAdmin', + inputs: [ + { + name: 'rateLimitAdmin', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setRebalancer', + inputs: [{ name: 'rebalancer', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setRouter', + inputs: [{ name: 'newRouter', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'supportsInterface', + inputs: [{ name: 'interfaceId', type: 'bytes4', internalType: 'bytes4' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'transferLiquidity', + inputs: [ + { name: 'from', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [{ name: 'to', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'typeAndVersion', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'withdrawLiquidity', + inputs: [{ name: 'amount', type: 'uint256', internalType: 'uint256' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'event', + name: 'AllowListAdd', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'AllowListRemove', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Burned', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + { + name: 'remoteToken', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainConfigured', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ConfigChanged', + inputs: [ + { + name: 'config', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'LiquidityAdded', + inputs: [ + { + name: 'provider', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: true, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'LiquidityRemoved', + inputs: [ + { + name: 'provider', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: true, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'LiquidityTransferred', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Locked', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Minted', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'recipient', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferRequested', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RateLimitAdminSet', + inputs: [ + { + name: 'rateLimitAdmin', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Released', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'recipient', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RouterUpdated', + inputs: [ + { + name: 'oldRouter', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'newRouter', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'TokensConsumed', + inputs: [ + { + name: 'tokens', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'error', + name: 'AggregateValueMaxCapacityExceeded', + inputs: [ + { name: 'capacity', type: 'uint256', internalType: 'uint256' }, + { name: 'requested', type: 'uint256', internalType: 'uint256' }, + ], + }, + { + type: 'error', + name: 'AggregateValueRateLimitReached', + inputs: [ + { + name: 'minWaitInSeconds', + type: 'uint256', + internalType: 'uint256', + }, + { name: 'available', type: 'uint256', internalType: 'uint256' }, + ], + }, + { type: 'error', name: 'AllowListNotEnabled', inputs: [] }, + { type: 'error', name: 'BucketOverfilled', inputs: [] }, + { + type: 'error', + name: 'CallerIsNotARampOnRouter', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'CannotTransferToSelf', inputs: [] }, + { + type: 'error', + name: 'ChainAlreadyExists', + inputs: [{ name: 'chainSelector', type: 'uint64', internalType: 'uint64' }], + }, + { + type: 'error', + name: 'ChainNotAllowed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'CursedByRMN', inputs: [] }, + { + type: 'error', + name: 'DisabledNonZeroRateLimit', + inputs: [ + { + name: 'config', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { type: 'error', name: 'InsufficientLiquidity', inputs: [] }, + { + type: 'error', + name: 'InvalidDecimalArgs', + inputs: [ + { name: 'expected', type: 'uint8', internalType: 'uint8' }, + { name: 'actual', type: 'uint8', internalType: 'uint8' }, + ], + }, + { + type: 'error', + name: 'InvalidRateLimitRate', + inputs: [ + { + name: 'rateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { + type: 'error', + name: 'InvalidRemoteChainDecimals', + inputs: [{ name: 'sourcePoolData', type: 'bytes', internalType: 'bytes' }], + }, + { + type: 'error', + name: 'InvalidRemotePoolForChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidSourcePoolAddress', + inputs: [ + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'LiquidityNotAccepted', inputs: [] }, + { type: 'error', name: 'MismatchedArrayLengths', inputs: [] }, + { type: 'error', name: 'MustBeProposedOwner', inputs: [] }, + { + type: 'error', + name: 'NonExistentChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'OnlyCallableByOwner', inputs: [] }, + { + type: 'error', + name: 'OverflowDetected', + inputs: [ + { name: 'remoteDecimals', type: 'uint8', internalType: 'uint8' }, + { name: 'localDecimals', type: 'uint8', internalType: 'uint8' }, + { + name: 'remoteAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { type: 'error', name: 'OwnerCannotBeZero', inputs: [] }, + { + type: 'error', + name: 'PoolAlreadyAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { type: 'error', name: 'RateLimitMustBeDisabled', inputs: [] }, + { + type: 'error', + name: 'SenderNotAllowed', + inputs: [{ name: 'sender', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'TokenMaxCapacityExceeded', + inputs: [ + { name: 'capacity', type: 'uint256', internalType: 'uint256' }, + { name: 'requested', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'TokenRateLimitReached', + inputs: [ + { + name: 'minWaitInSeconds', + type: 'uint256', + internalType: 'uint256', + }, + { name: 'available', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'Unauthorized', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'ZeroAddressNotAllowed', inputs: [] }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V1_6_0/registry-module-owner-custom.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V1_6_0/registry-module-owner-custom.ts new file mode 100644 index 000000000..ef36c1115 --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V1_6_0/registry-module-owner-custom.ts @@ -0,0 +1,84 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v1_6_0/registry_module_owner_custom.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + type: 'constructor', + inputs: [ + { + name: 'tokenAdminRegistry', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'registerAccessControlDefaultAdmin', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'registerAdminViaGetCCIPAdmin', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'registerAdminViaOwner', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'typeAndVersion', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'view', + }, + { + type: 'event', + name: 'AdministratorRegistered', + inputs: [ + { + name: 'token', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'administrator', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { type: 'error', name: 'AddressZero', inputs: [] }, + { + type: 'error', + name: 'CanOnlySelfRegister', + inputs: [ + { name: 'admin', type: 'address', internalType: 'address' }, + { name: 'token', type: 'address', internalType: 'address' }, + ], + }, + { + type: 'error', + name: 'RequiredRoleNotFound', + inputs: [ + { name: 'msgSender', type: 'address', internalType: 'address' }, + { name: 'role', type: 'bytes32', internalType: 'bytes32' }, + { name: 'token', type: 'address', internalType: 'address' }, + ], + }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V1_6_1/burn-mint-token-pool.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V1_6_1/burn-mint-token-pool.ts new file mode 100644 index 000000000..a9243ee8e --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V1_6_1/burn-mint-token-pool.ts @@ -0,0 +1,1171 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v1_6_1/burn_mint_token_pool.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + type: 'constructor', + inputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IBurnMintERC20', + }, + { + name: 'localTokenDecimals', + type: 'uint8', + internalType: 'uint8', + }, + { + name: 'allowlist', + type: 'address[]', + internalType: 'address[]', + }, + { name: 'rmnProxy', type: 'address', internalType: 'address' }, + { name: 'router', type: 'address', internalType: 'address' }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'acceptOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'addRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyAllowListUpdates', + inputs: [ + { name: 'removes', type: 'address[]', internalType: 'address[]' }, + { name: 'adds', type: 'address[]', internalType: 'address[]' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyChainUpdates', + inputs: [ + { + name: 'remoteChainSelectorsToRemove', + type: 'uint64[]', + internalType: 'uint64[]', + }, + { + name: 'chainsToAdd', + type: 'tuple[]', + internalType: 'struct TokenPool.ChainUpdate[]', + components: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddresses', + type: 'bytes[]', + internalType: 'bytes[]', + }, + { + name: 'remoteTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'getAllowList', + inputs: [], + outputs: [{ name: '', type: 'address[]', internalType: 'address[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getAllowListEnabled', + inputs: [], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCurrentInboundRateLimiterState', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCurrentOutboundRateLimiterState', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRateLimitAdmin', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemotePools', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes[]', internalType: 'bytes[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemoteToken', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes', internalType: 'bytes' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRmnProxy', + inputs: [], + outputs: [{ name: 'rmnProxy', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRouter', + inputs: [], + outputs: [{ name: 'router', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getSupportedChains', + inputs: [], + outputs: [{ name: '', type: 'uint64[]', internalType: 'uint64[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getToken', + inputs: [], + outputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IERC20', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getTokenDecimals', + inputs: [], + outputs: [{ name: 'decimals', type: 'uint8', internalType: 'uint8' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'lockOrBurn', + inputs: [ + { + name: 'lockOrBurnIn', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnInV1', + components: [ + { name: 'receiver', type: 'bytes', internalType: 'bytes' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'originalSender', + type: 'address', + internalType: 'address', + }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + ], + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnOutV1', + components: [ + { + name: 'destTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'destPoolData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'releaseOrMint', + inputs: [ + { + name: 'releaseOrMintIn', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintInV1', + components: [ + { + name: 'originalSender', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'receiver', + type: 'address', + internalType: 'address', + }, + { + name: 'sourceDenominatedAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'sourcePoolData', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'offchainTokenData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintOutV1', + components: [ + { + name: 'destinationAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'removeRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setChainRateLimiterConfig', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'outboundConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setChainRateLimiterConfigs', + inputs: [ + { + name: 'remoteChainSelectors', + type: 'uint64[]', + internalType: 'uint64[]', + }, + { + name: 'outboundConfigs', + type: 'tuple[]', + internalType: 'struct RateLimiter.Config[]', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundConfigs', + type: 'tuple[]', + internalType: 'struct RateLimiter.Config[]', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setRateLimitAdmin', + inputs: [ + { + name: 'rateLimitAdmin', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setRouter', + inputs: [{ name: 'newRouter', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'supportsInterface', + inputs: [{ name: 'interfaceId', type: 'bytes4', internalType: 'bytes4' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [{ name: 'to', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'typeAndVersion', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'view', + }, + { + type: 'event', + name: 'AllowListAdd', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'AllowListRemove', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + { + name: 'remoteToken', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainConfigured', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ConfigChanged', + inputs: [ + { + name: 'config', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'InboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'LockedOrBurned', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OutboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferRequested', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RateLimitAdminSet', + inputs: [ + { + name: 'rateLimitAdmin', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ReleasedOrMinted', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'recipient', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RouterUpdated', + inputs: [ + { + name: 'oldRouter', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'newRouter', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { type: 'error', name: 'AllowListNotEnabled', inputs: [] }, + { type: 'error', name: 'BucketOverfilled', inputs: [] }, + { + type: 'error', + name: 'CallerIsNotARampOnRouter', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'CannotTransferToSelf', inputs: [] }, + { + type: 'error', + name: 'ChainAlreadyExists', + inputs: [{ name: 'chainSelector', type: 'uint64', internalType: 'uint64' }], + }, + { + type: 'error', + name: 'ChainNotAllowed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'CursedByRMN', inputs: [] }, + { + type: 'error', + name: 'DisabledNonZeroRateLimit', + inputs: [ + { + name: 'config', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { + type: 'error', + name: 'InvalidDecimalArgs', + inputs: [ + { name: 'expected', type: 'uint8', internalType: 'uint8' }, + { name: 'actual', type: 'uint8', internalType: 'uint8' }, + ], + }, + { + type: 'error', + name: 'InvalidRateLimitRate', + inputs: [ + { + name: 'rateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { + type: 'error', + name: 'InvalidRemoteChainDecimals', + inputs: [{ name: 'sourcePoolData', type: 'bytes', internalType: 'bytes' }], + }, + { + type: 'error', + name: 'InvalidRemotePoolForChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidSourcePoolAddress', + inputs: [ + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'MismatchedArrayLengths', inputs: [] }, + { type: 'error', name: 'MustBeProposedOwner', inputs: [] }, + { + type: 'error', + name: 'NonExistentChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'OnlyCallableByOwner', inputs: [] }, + { + type: 'error', + name: 'OverflowDetected', + inputs: [ + { name: 'remoteDecimals', type: 'uint8', internalType: 'uint8' }, + { name: 'localDecimals', type: 'uint8', internalType: 'uint8' }, + { + name: 'remoteAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { type: 'error', name: 'OwnerCannotBeZero', inputs: [] }, + { + type: 'error', + name: 'PoolAlreadyAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'SenderNotAllowed', + inputs: [{ name: 'sender', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'TokenMaxCapacityExceeded', + inputs: [ + { name: 'capacity', type: 'uint256', internalType: 'uint256' }, + { name: 'requested', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'TokenRateLimitReached', + inputs: [ + { + name: 'minWaitInSeconds', + type: 'uint256', + internalType: 'uint256', + }, + { name: 'available', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'Unauthorized', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'ZeroAddressNotAllowed', inputs: [] }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V1_6_1/lock-release-token-pool.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V1_6_1/lock-release-token-pool.ts new file mode 100644 index 000000000..8863f833b --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V1_6_1/lock-release-token-pool.ts @@ -0,0 +1,1286 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v1_6_1/lock_release_token_pool.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + type: 'constructor', + inputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IERC20', + }, + { + name: 'localTokenDecimals', + type: 'uint8', + internalType: 'uint8', + }, + { + name: 'allowlist', + type: 'address[]', + internalType: 'address[]', + }, + { name: 'rmnProxy', type: 'address', internalType: 'address' }, + { name: 'router', type: 'address', internalType: 'address' }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'acceptOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'addRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyAllowListUpdates', + inputs: [ + { name: 'removes', type: 'address[]', internalType: 'address[]' }, + { name: 'adds', type: 'address[]', internalType: 'address[]' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyChainUpdates', + inputs: [ + { + name: 'remoteChainSelectorsToRemove', + type: 'uint64[]', + internalType: 'uint64[]', + }, + { + name: 'chainsToAdd', + type: 'tuple[]', + internalType: 'struct TokenPool.ChainUpdate[]', + components: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddresses', + type: 'bytes[]', + internalType: 'bytes[]', + }, + { + name: 'remoteTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'getAllowList', + inputs: [], + outputs: [{ name: '', type: 'address[]', internalType: 'address[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getAllowListEnabled', + inputs: [], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCurrentInboundRateLimiterState', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCurrentOutboundRateLimiterState', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRateLimitAdmin', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRebalancer', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemotePools', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes[]', internalType: 'bytes[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemoteToken', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes', internalType: 'bytes' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRmnProxy', + inputs: [], + outputs: [{ name: 'rmnProxy', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRouter', + inputs: [], + outputs: [{ name: 'router', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getSupportedChains', + inputs: [], + outputs: [{ name: '', type: 'uint64[]', internalType: 'uint64[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getToken', + inputs: [], + outputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IERC20', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getTokenDecimals', + inputs: [], + outputs: [{ name: 'decimals', type: 'uint8', internalType: 'uint8' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'lockOrBurn', + inputs: [ + { + name: 'lockOrBurnIn', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnInV1', + components: [ + { name: 'receiver', type: 'bytes', internalType: 'bytes' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'originalSender', + type: 'address', + internalType: 'address', + }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + ], + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnOutV1', + components: [ + { + name: 'destTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'destPoolData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'provideLiquidity', + inputs: [{ name: 'amount', type: 'uint256', internalType: 'uint256' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'releaseOrMint', + inputs: [ + { + name: 'releaseOrMintIn', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintInV1', + components: [ + { + name: 'originalSender', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'receiver', + type: 'address', + internalType: 'address', + }, + { + name: 'sourceDenominatedAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'sourcePoolData', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'offchainTokenData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintOutV1', + components: [ + { + name: 'destinationAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'removeRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setChainRateLimiterConfig', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'outboundConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setChainRateLimiterConfigs', + inputs: [ + { + name: 'remoteChainSelectors', + type: 'uint64[]', + internalType: 'uint64[]', + }, + { + name: 'outboundConfigs', + type: 'tuple[]', + internalType: 'struct RateLimiter.Config[]', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundConfigs', + type: 'tuple[]', + internalType: 'struct RateLimiter.Config[]', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setRateLimitAdmin', + inputs: [ + { + name: 'rateLimitAdmin', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setRebalancer', + inputs: [{ name: 'rebalancer', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setRouter', + inputs: [{ name: 'newRouter', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'supportsInterface', + inputs: [{ name: 'interfaceId', type: 'bytes4', internalType: 'bytes4' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'transferLiquidity', + inputs: [ + { name: 'from', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [{ name: 'to', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'typeAndVersion', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'withdrawLiquidity', + inputs: [{ name: 'amount', type: 'uint256', internalType: 'uint256' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'event', + name: 'AllowListAdd', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'AllowListRemove', + inputs: [ + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + { + name: 'remoteToken', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainConfigured', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ConfigChanged', + inputs: [ + { + name: 'config', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'InboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'LiquidityAdded', + inputs: [ + { + name: 'provider', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: true, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'LiquidityRemoved', + inputs: [ + { + name: 'provider', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: true, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'LiquidityTransferred', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'LockedOrBurned', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OutboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferRequested', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RateLimitAdminSet', + inputs: [ + { + name: 'rateLimitAdmin', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RebalancerSet', + inputs: [ + { + name: 'oldRebalancer', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'newRebalancer', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ReleasedOrMinted', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'recipient', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RouterUpdated', + inputs: [ + { + name: 'oldRouter', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'newRouter', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { type: 'error', name: 'AllowListNotEnabled', inputs: [] }, + { type: 'error', name: 'BucketOverfilled', inputs: [] }, + { + type: 'error', + name: 'CallerIsNotARampOnRouter', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'CannotTransferToSelf', inputs: [] }, + { + type: 'error', + name: 'ChainAlreadyExists', + inputs: [{ name: 'chainSelector', type: 'uint64', internalType: 'uint64' }], + }, + { + type: 'error', + name: 'ChainNotAllowed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'CursedByRMN', inputs: [] }, + { + type: 'error', + name: 'DisabledNonZeroRateLimit', + inputs: [ + { + name: 'config', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { type: 'error', name: 'InsufficientLiquidity', inputs: [] }, + { + type: 'error', + name: 'InvalidDecimalArgs', + inputs: [ + { name: 'expected', type: 'uint8', internalType: 'uint8' }, + { name: 'actual', type: 'uint8', internalType: 'uint8' }, + ], + }, + { + type: 'error', + name: 'InvalidRateLimitRate', + inputs: [ + { + name: 'rateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { + type: 'error', + name: 'InvalidRemoteChainDecimals', + inputs: [{ name: 'sourcePoolData', type: 'bytes', internalType: 'bytes' }], + }, + { + type: 'error', + name: 'InvalidRemotePoolForChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidSourcePoolAddress', + inputs: [ + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'MismatchedArrayLengths', inputs: [] }, + { type: 'error', name: 'MustBeProposedOwner', inputs: [] }, + { + type: 'error', + name: 'NonExistentChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'OnlyCallableByOwner', inputs: [] }, + { + type: 'error', + name: 'OverflowDetected', + inputs: [ + { name: 'remoteDecimals', type: 'uint8', internalType: 'uint8' }, + { name: 'localDecimals', type: 'uint8', internalType: 'uint8' }, + { + name: 'remoteAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { type: 'error', name: 'OwnerCannotBeZero', inputs: [] }, + { + type: 'error', + name: 'PoolAlreadyAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'SenderNotAllowed', + inputs: [{ name: 'sender', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'TokenMaxCapacityExceeded', + inputs: [ + { name: 'capacity', type: 'uint256', internalType: 'uint256' }, + { name: 'requested', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'TokenRateLimitReached', + inputs: [ + { + name: 'minWaitInSeconds', + type: 'uint256', + internalType: 'uint256', + }, + { name: 'available', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'Unauthorized', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'ZeroAddressNotAllowed', inputs: [] }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V1_6_2/factory-burn-mint-erc20.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V1_6_2/factory-burn-mint-erc20.ts new file mode 100644 index 000000000..5df804e9c --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V1_6_2/factory-burn-mint-erc20.ts @@ -0,0 +1,490 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v1_6_2/factory_burn_mint_erc20.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + type: 'constructor', + inputs: [ + { name: 'name', type: 'string', internalType: 'string' }, + { name: 'symbol', type: 'string', internalType: 'string' }, + { name: 'decimals_', type: 'uint8', internalType: 'uint8' }, + { name: 'maxSupply_', type: 'uint256', internalType: 'uint256' }, + { name: 'preMint', type: 'uint256', internalType: 'uint256' }, + { name: 'newOwner', type: 'address', internalType: 'address' }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'acceptOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'allowance', + inputs: [ + { name: 'owner', type: 'address', internalType: 'address' }, + { name: 'spender', type: 'address', internalType: 'address' }, + ], + outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'approve', + inputs: [ + { name: 'spender', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'balanceOf', + inputs: [{ name: 'account', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'burn', + inputs: [{ name: 'amount', type: 'uint256', internalType: 'uint256' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'burn', + inputs: [ + { name: 'account', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'burnFrom', + inputs: [ + { name: 'account', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'decimals', + inputs: [], + outputs: [{ name: '', type: 'uint8', internalType: 'uint8' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'decreaseAllowance', + inputs: [ + { name: 'spender', type: 'address', internalType: 'address' }, + { + name: 'subtractedValue', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'decreaseApproval', + inputs: [ + { name: 'spender', type: 'address', internalType: 'address' }, + { + name: 'subtractedValue', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [{ name: 'success', type: 'bool', internalType: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'getBurners', + inputs: [], + outputs: [{ name: '', type: 'address[]', internalType: 'address[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCCIPAdmin', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getMinters', + inputs: [], + outputs: [{ name: '', type: 'address[]', internalType: 'address[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'grantBurnRole', + inputs: [{ name: 'burner', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'grantMintAndBurnRoles', + inputs: [ + { + name: 'burnAndMinter', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'grantMintRole', + inputs: [{ name: 'minter', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'increaseAllowance', + inputs: [ + { name: 'spender', type: 'address', internalType: 'address' }, + { name: 'addedValue', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'increaseApproval', + inputs: [ + { name: 'spender', type: 'address', internalType: 'address' }, + { name: 'addedValue', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'isBurner', + inputs: [{ name: 'burner', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isMinter', + inputs: [{ name: 'minter', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'maxSupply', + inputs: [], + outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'mint', + inputs: [ + { name: 'account', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'name', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'revokeBurnRole', + inputs: [{ name: 'burner', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'revokeMintRole', + inputs: [{ name: 'minter', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setCCIPAdmin', + inputs: [{ name: 'newAdmin', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'supportsInterface', + inputs: [{ name: 'interfaceId', type: 'bytes4', internalType: 'bytes4' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'symbol', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'totalSupply', + inputs: [], + outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'transfer', + inputs: [ + { name: 'to', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'transferFrom', + inputs: [ + { name: 'from', type: 'address', internalType: 'address' }, + { name: 'to', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [{ name: 'to', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'typeAndVersion', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'pure', + }, + { + type: 'event', + name: 'Approval', + inputs: [ + { + name: 'owner', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'spender', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'BurnAccessGranted', + inputs: [ + { + name: 'burner', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'BurnAccessRevoked', + inputs: [ + { + name: 'burner', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'CCIPAdminTransferred', + inputs: [ + { + name: 'previousAdmin', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'newAdmin', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'MintAccessGranted', + inputs: [ + { + name: 'minter', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'MintAccessRevoked', + inputs: [ + { + name: 'minter', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferRequested', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Transfer', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { type: 'error', name: 'CannotTransferToSelf', inputs: [] }, + { + type: 'error', + name: 'MaxSupplyExceeded', + inputs: [ + { + name: 'supplyAfterMint', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { type: 'error', name: 'MustBeProposedOwner', inputs: [] }, + { type: 'error', name: 'OnlyCallableByOwner', inputs: [] }, + { type: 'error', name: 'OwnerCannotBeZero', inputs: [] }, + { + type: 'error', + name: 'SenderNotBurner', + inputs: [{ name: 'sender', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'SenderNotMinter', + inputs: [{ name: 'sender', type: 'address', internalType: 'address' }], + }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/burn-from-mint-token-pool.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/burn-from-mint-token-pool.ts new file mode 100644 index 000000000..e945540de --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/burn-from-mint-token-pool.ts @@ -0,0 +1,1665 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v2_0_0/burn_from_mint_token_pool.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + type: 'constructor', + inputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IBurnMintERC20', + }, + { + name: 'localTokenDecimals', + type: 'uint8', + internalType: 'uint8', + }, + { + name: 'advancedPoolHooks', + type: 'address', + internalType: 'address', + }, + { name: 'rmnProxy', type: 'address', internalType: 'address' }, + { name: 'router', type: 'address', internalType: 'address' }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'acceptOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'addRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyChainUpdates', + inputs: [ + { + name: 'remoteChainSelectorsToRemove', + type: 'uint64[]', + internalType: 'uint64[]', + }, + { + name: 'chainsToAdd', + type: 'tuple[]', + internalType: 'struct TokenPool.ChainUpdate[]', + components: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddresses', + type: 'bytes[]', + internalType: 'bytes[]', + }, + { + name: 'remoteTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyTokenTransferFeeConfigUpdates', + inputs: [ + { + name: 'tokenTransferFeeConfigArgs', + type: 'tuple[]', + internalType: 'struct TokenPool.TokenTransferFeeConfigArgs[]', + components: [ + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'tokenTransferFeeConfig', + type: 'tuple', + internalType: 'struct IPoolV2.TokenTransferFeeConfig', + components: [ + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'fastFinalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { + name: 'fastFinalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + }, + ], + }, + { + name: 'disableTokenTransferFeeConfigs', + type: 'uint64[]', + internalType: 'uint64[]', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'getAdvancedPoolHooks', + inputs: [], + outputs: [ + { + name: 'advancedPoolHook', + type: 'address', + internalType: 'contract IAdvancedPoolHooks', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getAllowedFinalityConfig', + inputs: [], + outputs: [ + { + name: 'allowedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCurrentRateLimiterState', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: 'fastFinality', type: 'bool', internalType: 'bool' }, + ], + outputs: [ + { + name: 'outboundRateLimiterState', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterState', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getDynamicConfig', + inputs: [], + outputs: [ + { name: 'router', type: 'address', internalType: 'address' }, + { + name: 'rateLimitAdmin', + type: 'address', + internalType: 'address', + }, + { name: 'feeAdmin', type: 'address', internalType: 'address' }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getFee', + inputs: [ + { name: '', type: 'address', internalType: 'address' }, + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: '', type: 'uint256', internalType: 'uint256' }, + { name: '', type: 'address', internalType: 'address' }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + { name: '', type: 'bytes', internalType: 'bytes' }, + ], + outputs: [ + { name: 'feeUSDCents', type: 'uint256', internalType: 'uint256' }, + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'tokenFeeBps', type: 'uint16', internalType: 'uint16' }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemotePools', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes[]', internalType: 'bytes[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemoteToken', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes', internalType: 'bytes' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRequiredCCVs', + inputs: [ + { name: 'localToken', type: 'address', internalType: 'address' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'sourceDenominatedAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + { name: 'extraData', type: 'bytes', internalType: 'bytes' }, + { + name: 'direction', + type: 'uint8', + internalType: 'enum IPoolV2.MessageDirection', + }, + ], + outputs: [ + { + name: 'requiredCCVs', + type: 'address[]', + internalType: 'address[]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRmnProxy', + inputs: [], + outputs: [{ name: 'rmnProxy', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getSupportedChains', + inputs: [], + outputs: [{ name: '', type: 'uint64[]', internalType: 'uint64[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getToken', + inputs: [], + outputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IERC20', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getTokenDecimals', + inputs: [], + outputs: [{ name: 'decimals', type: 'uint8', internalType: 'uint8' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getTokenTransferFeeConfig', + inputs: [ + { name: '', type: 'address', internalType: 'address' }, + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: '', type: 'bytes4', internalType: 'bytes4' }, + { name: '', type: 'bytes', internalType: 'bytes' }, + ], + outputs: [ + { + name: 'feeConfig', + type: 'tuple', + internalType: 'struct IPoolV2.TokenTransferFeeConfig', + components: [ + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'fastFinalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { + name: 'fastFinalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'lockOrBurn', + inputs: [ + { + name: 'lockOrBurnIn', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnInV1', + components: [ + { name: 'receiver', type: 'bytes', internalType: 'bytes' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'originalSender', + type: 'address', + internalType: 'address', + }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + ], + }, + ], + outputs: [ + { + name: 'lockOrBurnOutV1', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnOutV1', + components: [ + { + name: 'destTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'destPoolData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'lockOrBurn', + inputs: [ + { + name: 'lockOrBurnIn', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnInV1', + components: [ + { name: 'receiver', type: 'bytes', internalType: 'bytes' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'originalSender', + type: 'address', + internalType: 'address', + }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + ], + }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + { name: 'tokenArgs', type: 'bytes', internalType: 'bytes' }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnOutV1', + components: [ + { + name: 'destTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'destPoolData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + name: 'destTokenAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'releaseOrMint', + inputs: [ + { + name: 'releaseOrMintIn', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintInV1', + components: [ + { + name: 'originalSender', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'receiver', + type: 'address', + internalType: 'address', + }, + { + name: 'sourceDenominatedAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'sourcePoolData', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'offchainTokenData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintOutV1', + components: [ + { + name: 'destinationAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'releaseOrMint', + inputs: [ + { + name: 'releaseOrMintIn', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintInV1', + components: [ + { + name: 'originalSender', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'receiver', + type: 'address', + internalType: 'address', + }, + { + name: 'sourceDenominatedAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'sourcePoolData', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'offchainTokenData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintOutV1', + components: [ + { + name: 'destinationAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'removeRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setAllowedFinalityConfig', + inputs: [ + { + name: 'allowedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setDynamicConfig', + inputs: [ + { name: 'router', type: 'address', internalType: 'address' }, + { + name: 'rateLimitAdmin', + type: 'address', + internalType: 'address', + }, + { name: 'feeAdmin', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setRateLimitConfig', + inputs: [ + { + name: 'rateLimitConfigArgs', + type: 'tuple[]', + internalType: 'struct TokenPool.RateLimitConfigArgs[]', + components: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: 'fastFinality', type: 'bool', internalType: 'bool' }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'supportsInterface', + inputs: [{ name: 'interfaceId', type: 'bytes4', internalType: 'bytes4' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [{ name: 'to', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'typeAndVersion', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'updateAdvancedPoolHooks', + inputs: [ + { + name: 'newHook', + type: 'address', + internalType: 'contract IAdvancedPoolHooks', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'withdrawFeeTokens', + inputs: [ + { + name: 'feeTokens', + type: 'address[]', + internalType: 'address[]', + }, + { name: 'recipient', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'event', + name: 'AdvancedPoolHooksUpdated', + inputs: [ + { + name: 'oldHook', + type: 'address', + indexed: false, + internalType: 'contract IAdvancedPoolHooks', + }, + { + name: 'newHook', + type: 'address', + indexed: false, + internalType: 'contract IAdvancedPoolHooks', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + { + name: 'remoteToken', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'DynamicConfigSet', + inputs: [ + { + name: 'router', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'rateLimitAdmin', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'feeAdmin', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FastFinalityInboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FastFinalityOutboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FeeTokenWithdrawn', + inputs: [ + { + name: 'receiver', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'feeToken', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FinalityConfigSet', + inputs: [ + { + name: 'allowedFinality', + type: 'bytes4', + indexed: false, + internalType: 'bytes4', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'InboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'LockedOrBurned', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OutboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferRequested', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RateLimitConfigured', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'fastFinality', + type: 'bool', + indexed: false, + internalType: 'bool', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ReleasedOrMinted', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'recipient', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'TokenTransferFeeConfigDeleted', + inputs: [ + { + name: 'destChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'TokenTransferFeeConfigUpdated', + inputs: [ + { + name: 'destChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'tokenTransferFeeConfig', + type: 'tuple', + indexed: false, + internalType: 'struct IPoolV2.TokenTransferFeeConfig', + components: [ + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'fastFinalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { + name: 'fastFinalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + }, + ], + anonymous: false, + }, + { type: 'error', name: 'BucketOverfilled', inputs: [] }, + { + type: 'error', + name: 'CallerIsNotARampOnRouter', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'CallerIsNotOwnerOrFeeAdmin', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'CannotTransferToSelf', inputs: [] }, + { + type: 'error', + name: 'ChainAlreadyExists', + inputs: [{ name: 'chainSelector', type: 'uint64', internalType: 'uint64' }], + }, + { + type: 'error', + name: 'ChainNotAllowed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'CursedByRMN', inputs: [] }, + { + type: 'error', + name: 'DisabledNonZeroRateLimit', + inputs: [ + { + name: 'config', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { + type: 'error', + name: 'InvalidDecimalArgs', + inputs: [ + { name: 'expected', type: 'uint8', internalType: 'uint8' }, + { name: 'actual', type: 'uint8', internalType: 'uint8' }, + ], + }, + { + type: 'error', + name: 'InvalidRateLimitRate', + inputs: [ + { + name: 'rateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { + type: 'error', + name: 'InvalidRemoteChainDecimals', + inputs: [{ name: 'sourcePoolData', type: 'bytes', internalType: 'bytes' }], + }, + { + type: 'error', + name: 'InvalidRemotePoolForChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidRequestedFinality', + inputs: [ + { + name: 'requestedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + { + name: 'allowedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + }, + { + type: 'error', + name: 'InvalidSourcePoolAddress', + inputs: [ + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'InvalidTokenTransferFeeConfig', + inputs: [ + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { + type: 'error', + name: 'InvalidTransferFeeBps', + inputs: [{ name: 'bps', type: 'uint256', internalType: 'uint256' }], + }, + { type: 'error', name: 'MustBeProposedOwner', inputs: [] }, + { + type: 'error', + name: 'NonExistentChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'OnlyCallableByOwner', inputs: [] }, + { + type: 'error', + name: 'OverflowDetected', + inputs: [ + { name: 'remoteDecimals', type: 'uint8', internalType: 'uint8' }, + { name: 'localDecimals', type: 'uint8', internalType: 'uint8' }, + { + name: 'remoteAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { type: 'error', name: 'OwnerCannotBeZero', inputs: [] }, + { + type: 'error', + name: 'PoolAlreadyAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'RequestedFinalityCanOnlyHaveOneMode', + inputs: [ + { + name: 'encodedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + }, + { + type: 'error', + name: 'SafeERC20FailedOperation', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'TokenMaxCapacityExceeded', + inputs: [ + { name: 'capacity', type: 'uint256', internalType: 'uint256' }, + { name: 'requested', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'TokenRateLimitReached', + inputs: [ + { + name: 'minWaitInSeconds', + type: 'uint256', + internalType: 'uint256', + }, + { name: 'available', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'Unauthorized', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'ZeroAddressInvalid', inputs: [] }, + { type: 'error', name: 'ZeroAddressNotAllowed', inputs: [] }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/burn-mint-token-pool.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/burn-mint-token-pool.ts new file mode 100644 index 000000000..ab3df1049 --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/burn-mint-token-pool.ts @@ -0,0 +1,1665 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v2_0_0/burn_mint_token_pool.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + type: 'constructor', + inputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IBurnMintERC20', + }, + { + name: 'localTokenDecimals', + type: 'uint8', + internalType: 'uint8', + }, + { + name: 'advancedPoolHooks', + type: 'address', + internalType: 'address', + }, + { name: 'rmnProxy', type: 'address', internalType: 'address' }, + { name: 'router', type: 'address', internalType: 'address' }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'acceptOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'addRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyChainUpdates', + inputs: [ + { + name: 'remoteChainSelectorsToRemove', + type: 'uint64[]', + internalType: 'uint64[]', + }, + { + name: 'chainsToAdd', + type: 'tuple[]', + internalType: 'struct TokenPool.ChainUpdate[]', + components: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddresses', + type: 'bytes[]', + internalType: 'bytes[]', + }, + { + name: 'remoteTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyTokenTransferFeeConfigUpdates', + inputs: [ + { + name: 'tokenTransferFeeConfigArgs', + type: 'tuple[]', + internalType: 'struct TokenPool.TokenTransferFeeConfigArgs[]', + components: [ + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'tokenTransferFeeConfig', + type: 'tuple', + internalType: 'struct IPoolV2.TokenTransferFeeConfig', + components: [ + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'fastFinalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { + name: 'fastFinalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + }, + ], + }, + { + name: 'disableTokenTransferFeeConfigs', + type: 'uint64[]', + internalType: 'uint64[]', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'getAdvancedPoolHooks', + inputs: [], + outputs: [ + { + name: 'advancedPoolHook', + type: 'address', + internalType: 'contract IAdvancedPoolHooks', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getAllowedFinalityConfig', + inputs: [], + outputs: [ + { + name: 'allowedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCurrentRateLimiterState', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: 'fastFinality', type: 'bool', internalType: 'bool' }, + ], + outputs: [ + { + name: 'outboundRateLimiterState', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterState', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getDynamicConfig', + inputs: [], + outputs: [ + { name: 'router', type: 'address', internalType: 'address' }, + { + name: 'rateLimitAdmin', + type: 'address', + internalType: 'address', + }, + { name: 'feeAdmin', type: 'address', internalType: 'address' }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getFee', + inputs: [ + { name: '', type: 'address', internalType: 'address' }, + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: '', type: 'uint256', internalType: 'uint256' }, + { name: '', type: 'address', internalType: 'address' }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + { name: '', type: 'bytes', internalType: 'bytes' }, + ], + outputs: [ + { name: 'feeUSDCents', type: 'uint256', internalType: 'uint256' }, + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'tokenFeeBps', type: 'uint16', internalType: 'uint16' }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemotePools', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes[]', internalType: 'bytes[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemoteToken', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes', internalType: 'bytes' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRequiredCCVs', + inputs: [ + { name: 'localToken', type: 'address', internalType: 'address' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'sourceDenominatedAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + { name: 'extraData', type: 'bytes', internalType: 'bytes' }, + { + name: 'direction', + type: 'uint8', + internalType: 'enum IPoolV2.MessageDirection', + }, + ], + outputs: [ + { + name: 'requiredCCVs', + type: 'address[]', + internalType: 'address[]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRmnProxy', + inputs: [], + outputs: [{ name: 'rmnProxy', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getSupportedChains', + inputs: [], + outputs: [{ name: '', type: 'uint64[]', internalType: 'uint64[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getToken', + inputs: [], + outputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IERC20', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getTokenDecimals', + inputs: [], + outputs: [{ name: 'decimals', type: 'uint8', internalType: 'uint8' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getTokenTransferFeeConfig', + inputs: [ + { name: '', type: 'address', internalType: 'address' }, + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: '', type: 'bytes4', internalType: 'bytes4' }, + { name: '', type: 'bytes', internalType: 'bytes' }, + ], + outputs: [ + { + name: 'feeConfig', + type: 'tuple', + internalType: 'struct IPoolV2.TokenTransferFeeConfig', + components: [ + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'fastFinalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { + name: 'fastFinalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'lockOrBurn', + inputs: [ + { + name: 'lockOrBurnIn', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnInV1', + components: [ + { name: 'receiver', type: 'bytes', internalType: 'bytes' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'originalSender', + type: 'address', + internalType: 'address', + }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + ], + }, + ], + outputs: [ + { + name: 'lockOrBurnOutV1', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnOutV1', + components: [ + { + name: 'destTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'destPoolData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'lockOrBurn', + inputs: [ + { + name: 'lockOrBurnIn', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnInV1', + components: [ + { name: 'receiver', type: 'bytes', internalType: 'bytes' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'originalSender', + type: 'address', + internalType: 'address', + }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + ], + }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + { name: 'tokenArgs', type: 'bytes', internalType: 'bytes' }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnOutV1', + components: [ + { + name: 'destTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'destPoolData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + name: 'destTokenAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'releaseOrMint', + inputs: [ + { + name: 'releaseOrMintIn', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintInV1', + components: [ + { + name: 'originalSender', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'receiver', + type: 'address', + internalType: 'address', + }, + { + name: 'sourceDenominatedAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'sourcePoolData', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'offchainTokenData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintOutV1', + components: [ + { + name: 'destinationAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'releaseOrMint', + inputs: [ + { + name: 'releaseOrMintIn', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintInV1', + components: [ + { + name: 'originalSender', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'receiver', + type: 'address', + internalType: 'address', + }, + { + name: 'sourceDenominatedAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'sourcePoolData', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'offchainTokenData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintOutV1', + components: [ + { + name: 'destinationAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'removeRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setAllowedFinalityConfig', + inputs: [ + { + name: 'allowedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setDynamicConfig', + inputs: [ + { name: 'router', type: 'address', internalType: 'address' }, + { + name: 'rateLimitAdmin', + type: 'address', + internalType: 'address', + }, + { name: 'feeAdmin', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setRateLimitConfig', + inputs: [ + { + name: 'rateLimitConfigArgs', + type: 'tuple[]', + internalType: 'struct TokenPool.RateLimitConfigArgs[]', + components: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: 'fastFinality', type: 'bool', internalType: 'bool' }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'supportsInterface', + inputs: [{ name: 'interfaceId', type: 'bytes4', internalType: 'bytes4' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [{ name: 'to', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'typeAndVersion', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'updateAdvancedPoolHooks', + inputs: [ + { + name: 'newHook', + type: 'address', + internalType: 'contract IAdvancedPoolHooks', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'withdrawFeeTokens', + inputs: [ + { + name: 'feeTokens', + type: 'address[]', + internalType: 'address[]', + }, + { name: 'recipient', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'event', + name: 'AdvancedPoolHooksUpdated', + inputs: [ + { + name: 'oldHook', + type: 'address', + indexed: false, + internalType: 'contract IAdvancedPoolHooks', + }, + { + name: 'newHook', + type: 'address', + indexed: false, + internalType: 'contract IAdvancedPoolHooks', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + { + name: 'remoteToken', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'DynamicConfigSet', + inputs: [ + { + name: 'router', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'rateLimitAdmin', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'feeAdmin', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FastFinalityInboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FastFinalityOutboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FeeTokenWithdrawn', + inputs: [ + { + name: 'receiver', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'feeToken', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FinalityConfigSet', + inputs: [ + { + name: 'allowedFinality', + type: 'bytes4', + indexed: false, + internalType: 'bytes4', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'InboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'LockedOrBurned', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OutboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferRequested', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RateLimitConfigured', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'fastFinality', + type: 'bool', + indexed: false, + internalType: 'bool', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ReleasedOrMinted', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'recipient', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'TokenTransferFeeConfigDeleted', + inputs: [ + { + name: 'destChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'TokenTransferFeeConfigUpdated', + inputs: [ + { + name: 'destChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'tokenTransferFeeConfig', + type: 'tuple', + indexed: false, + internalType: 'struct IPoolV2.TokenTransferFeeConfig', + components: [ + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'fastFinalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { + name: 'fastFinalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + }, + ], + anonymous: false, + }, + { type: 'error', name: 'BucketOverfilled', inputs: [] }, + { + type: 'error', + name: 'CallerIsNotARampOnRouter', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'CallerIsNotOwnerOrFeeAdmin', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'CannotTransferToSelf', inputs: [] }, + { + type: 'error', + name: 'ChainAlreadyExists', + inputs: [{ name: 'chainSelector', type: 'uint64', internalType: 'uint64' }], + }, + { + type: 'error', + name: 'ChainNotAllowed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'CursedByRMN', inputs: [] }, + { + type: 'error', + name: 'DisabledNonZeroRateLimit', + inputs: [ + { + name: 'config', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { + type: 'error', + name: 'InvalidDecimalArgs', + inputs: [ + { name: 'expected', type: 'uint8', internalType: 'uint8' }, + { name: 'actual', type: 'uint8', internalType: 'uint8' }, + ], + }, + { + type: 'error', + name: 'InvalidRateLimitRate', + inputs: [ + { + name: 'rateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { + type: 'error', + name: 'InvalidRemoteChainDecimals', + inputs: [{ name: 'sourcePoolData', type: 'bytes', internalType: 'bytes' }], + }, + { + type: 'error', + name: 'InvalidRemotePoolForChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidRequestedFinality', + inputs: [ + { + name: 'requestedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + { + name: 'allowedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + }, + { + type: 'error', + name: 'InvalidSourcePoolAddress', + inputs: [ + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'InvalidTokenTransferFeeConfig', + inputs: [ + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { + type: 'error', + name: 'InvalidTransferFeeBps', + inputs: [{ name: 'bps', type: 'uint256', internalType: 'uint256' }], + }, + { type: 'error', name: 'MustBeProposedOwner', inputs: [] }, + { + type: 'error', + name: 'NonExistentChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'OnlyCallableByOwner', inputs: [] }, + { + type: 'error', + name: 'OverflowDetected', + inputs: [ + { name: 'remoteDecimals', type: 'uint8', internalType: 'uint8' }, + { name: 'localDecimals', type: 'uint8', internalType: 'uint8' }, + { + name: 'remoteAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { type: 'error', name: 'OwnerCannotBeZero', inputs: [] }, + { + type: 'error', + name: 'PoolAlreadyAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'RequestedFinalityCanOnlyHaveOneMode', + inputs: [ + { + name: 'encodedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + }, + { + type: 'error', + name: 'SafeERC20FailedOperation', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'TokenMaxCapacityExceeded', + inputs: [ + { name: 'capacity', type: 'uint256', internalType: 'uint256' }, + { name: 'requested', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'TokenRateLimitReached', + inputs: [ + { + name: 'minWaitInSeconds', + type: 'uint256', + internalType: 'uint256', + }, + { name: 'available', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'Unauthorized', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'ZeroAddressInvalid', inputs: [] }, + { type: 'error', name: 'ZeroAddressNotAllowed', inputs: [] }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/burn-with-from-mint-token-pool.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/burn-with-from-mint-token-pool.ts new file mode 100644 index 000000000..b5cfe27d7 --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/burn-with-from-mint-token-pool.ts @@ -0,0 +1,1665 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v2_0_0/burn_with_from_mint_token_pool.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + type: 'constructor', + inputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IBurnMintERC20', + }, + { + name: 'localTokenDecimals', + type: 'uint8', + internalType: 'uint8', + }, + { + name: 'advancedPoolHooks', + type: 'address', + internalType: 'address', + }, + { name: 'rmnProxy', type: 'address', internalType: 'address' }, + { name: 'router', type: 'address', internalType: 'address' }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'acceptOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'addRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyChainUpdates', + inputs: [ + { + name: 'remoteChainSelectorsToRemove', + type: 'uint64[]', + internalType: 'uint64[]', + }, + { + name: 'chainsToAdd', + type: 'tuple[]', + internalType: 'struct TokenPool.ChainUpdate[]', + components: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddresses', + type: 'bytes[]', + internalType: 'bytes[]', + }, + { + name: 'remoteTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyTokenTransferFeeConfigUpdates', + inputs: [ + { + name: 'tokenTransferFeeConfigArgs', + type: 'tuple[]', + internalType: 'struct TokenPool.TokenTransferFeeConfigArgs[]', + components: [ + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'tokenTransferFeeConfig', + type: 'tuple', + internalType: 'struct IPoolV2.TokenTransferFeeConfig', + components: [ + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'fastFinalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { + name: 'fastFinalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + }, + ], + }, + { + name: 'disableTokenTransferFeeConfigs', + type: 'uint64[]', + internalType: 'uint64[]', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'getAdvancedPoolHooks', + inputs: [], + outputs: [ + { + name: 'advancedPoolHook', + type: 'address', + internalType: 'contract IAdvancedPoolHooks', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getAllowedFinalityConfig', + inputs: [], + outputs: [ + { + name: 'allowedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCurrentRateLimiterState', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: 'fastFinality', type: 'bool', internalType: 'bool' }, + ], + outputs: [ + { + name: 'outboundRateLimiterState', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterState', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getDynamicConfig', + inputs: [], + outputs: [ + { name: 'router', type: 'address', internalType: 'address' }, + { + name: 'rateLimitAdmin', + type: 'address', + internalType: 'address', + }, + { name: 'feeAdmin', type: 'address', internalType: 'address' }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getFee', + inputs: [ + { name: '', type: 'address', internalType: 'address' }, + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: '', type: 'uint256', internalType: 'uint256' }, + { name: '', type: 'address', internalType: 'address' }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + { name: '', type: 'bytes', internalType: 'bytes' }, + ], + outputs: [ + { name: 'feeUSDCents', type: 'uint256', internalType: 'uint256' }, + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'tokenFeeBps', type: 'uint16', internalType: 'uint16' }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemotePools', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes[]', internalType: 'bytes[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemoteToken', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes', internalType: 'bytes' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRequiredCCVs', + inputs: [ + { name: 'localToken', type: 'address', internalType: 'address' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'sourceDenominatedAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + { name: 'extraData', type: 'bytes', internalType: 'bytes' }, + { + name: 'direction', + type: 'uint8', + internalType: 'enum IPoolV2.MessageDirection', + }, + ], + outputs: [ + { + name: 'requiredCCVs', + type: 'address[]', + internalType: 'address[]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRmnProxy', + inputs: [], + outputs: [{ name: 'rmnProxy', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getSupportedChains', + inputs: [], + outputs: [{ name: '', type: 'uint64[]', internalType: 'uint64[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getToken', + inputs: [], + outputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IERC20', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getTokenDecimals', + inputs: [], + outputs: [{ name: 'decimals', type: 'uint8', internalType: 'uint8' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getTokenTransferFeeConfig', + inputs: [ + { name: '', type: 'address', internalType: 'address' }, + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: '', type: 'bytes4', internalType: 'bytes4' }, + { name: '', type: 'bytes', internalType: 'bytes' }, + ], + outputs: [ + { + name: 'feeConfig', + type: 'tuple', + internalType: 'struct IPoolV2.TokenTransferFeeConfig', + components: [ + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'fastFinalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { + name: 'fastFinalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'lockOrBurn', + inputs: [ + { + name: 'lockOrBurnIn', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnInV1', + components: [ + { name: 'receiver', type: 'bytes', internalType: 'bytes' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'originalSender', + type: 'address', + internalType: 'address', + }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + ], + }, + ], + outputs: [ + { + name: 'lockOrBurnOutV1', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnOutV1', + components: [ + { + name: 'destTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'destPoolData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'lockOrBurn', + inputs: [ + { + name: 'lockOrBurnIn', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnInV1', + components: [ + { name: 'receiver', type: 'bytes', internalType: 'bytes' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'originalSender', + type: 'address', + internalType: 'address', + }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + ], + }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + { name: 'tokenArgs', type: 'bytes', internalType: 'bytes' }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnOutV1', + components: [ + { + name: 'destTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'destPoolData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + name: 'destTokenAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'releaseOrMint', + inputs: [ + { + name: 'releaseOrMintIn', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintInV1', + components: [ + { + name: 'originalSender', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'receiver', + type: 'address', + internalType: 'address', + }, + { + name: 'sourceDenominatedAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'sourcePoolData', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'offchainTokenData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintOutV1', + components: [ + { + name: 'destinationAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'releaseOrMint', + inputs: [ + { + name: 'releaseOrMintIn', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintInV1', + components: [ + { + name: 'originalSender', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'receiver', + type: 'address', + internalType: 'address', + }, + { + name: 'sourceDenominatedAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'sourcePoolData', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'offchainTokenData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintOutV1', + components: [ + { + name: 'destinationAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'removeRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setAllowedFinalityConfig', + inputs: [ + { + name: 'allowedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setDynamicConfig', + inputs: [ + { name: 'router', type: 'address', internalType: 'address' }, + { + name: 'rateLimitAdmin', + type: 'address', + internalType: 'address', + }, + { name: 'feeAdmin', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setRateLimitConfig', + inputs: [ + { + name: 'rateLimitConfigArgs', + type: 'tuple[]', + internalType: 'struct TokenPool.RateLimitConfigArgs[]', + components: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: 'fastFinality', type: 'bool', internalType: 'bool' }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'supportsInterface', + inputs: [{ name: 'interfaceId', type: 'bytes4', internalType: 'bytes4' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [{ name: 'to', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'typeAndVersion', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'updateAdvancedPoolHooks', + inputs: [ + { + name: 'newHook', + type: 'address', + internalType: 'contract IAdvancedPoolHooks', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'withdrawFeeTokens', + inputs: [ + { + name: 'feeTokens', + type: 'address[]', + internalType: 'address[]', + }, + { name: 'recipient', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'event', + name: 'AdvancedPoolHooksUpdated', + inputs: [ + { + name: 'oldHook', + type: 'address', + indexed: false, + internalType: 'contract IAdvancedPoolHooks', + }, + { + name: 'newHook', + type: 'address', + indexed: false, + internalType: 'contract IAdvancedPoolHooks', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + { + name: 'remoteToken', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'DynamicConfigSet', + inputs: [ + { + name: 'router', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'rateLimitAdmin', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'feeAdmin', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FastFinalityInboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FastFinalityOutboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FeeTokenWithdrawn', + inputs: [ + { + name: 'receiver', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'feeToken', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FinalityConfigSet', + inputs: [ + { + name: 'allowedFinality', + type: 'bytes4', + indexed: false, + internalType: 'bytes4', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'InboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'LockedOrBurned', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OutboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferRequested', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RateLimitConfigured', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'fastFinality', + type: 'bool', + indexed: false, + internalType: 'bool', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ReleasedOrMinted', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'recipient', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'TokenTransferFeeConfigDeleted', + inputs: [ + { + name: 'destChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'TokenTransferFeeConfigUpdated', + inputs: [ + { + name: 'destChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'tokenTransferFeeConfig', + type: 'tuple', + indexed: false, + internalType: 'struct IPoolV2.TokenTransferFeeConfig', + components: [ + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'fastFinalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { + name: 'fastFinalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + }, + ], + anonymous: false, + }, + { type: 'error', name: 'BucketOverfilled', inputs: [] }, + { + type: 'error', + name: 'CallerIsNotARampOnRouter', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'CallerIsNotOwnerOrFeeAdmin', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'CannotTransferToSelf', inputs: [] }, + { + type: 'error', + name: 'ChainAlreadyExists', + inputs: [{ name: 'chainSelector', type: 'uint64', internalType: 'uint64' }], + }, + { + type: 'error', + name: 'ChainNotAllowed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'CursedByRMN', inputs: [] }, + { + type: 'error', + name: 'DisabledNonZeroRateLimit', + inputs: [ + { + name: 'config', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { + type: 'error', + name: 'InvalidDecimalArgs', + inputs: [ + { name: 'expected', type: 'uint8', internalType: 'uint8' }, + { name: 'actual', type: 'uint8', internalType: 'uint8' }, + ], + }, + { + type: 'error', + name: 'InvalidRateLimitRate', + inputs: [ + { + name: 'rateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { + type: 'error', + name: 'InvalidRemoteChainDecimals', + inputs: [{ name: 'sourcePoolData', type: 'bytes', internalType: 'bytes' }], + }, + { + type: 'error', + name: 'InvalidRemotePoolForChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidRequestedFinality', + inputs: [ + { + name: 'requestedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + { + name: 'allowedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + }, + { + type: 'error', + name: 'InvalidSourcePoolAddress', + inputs: [ + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'InvalidTokenTransferFeeConfig', + inputs: [ + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { + type: 'error', + name: 'InvalidTransferFeeBps', + inputs: [{ name: 'bps', type: 'uint256', internalType: 'uint256' }], + }, + { type: 'error', name: 'MustBeProposedOwner', inputs: [] }, + { + type: 'error', + name: 'NonExistentChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'OnlyCallableByOwner', inputs: [] }, + { + type: 'error', + name: 'OverflowDetected', + inputs: [ + { name: 'remoteDecimals', type: 'uint8', internalType: 'uint8' }, + { name: 'localDecimals', type: 'uint8', internalType: 'uint8' }, + { + name: 'remoteAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { type: 'error', name: 'OwnerCannotBeZero', inputs: [] }, + { + type: 'error', + name: 'PoolAlreadyAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'RequestedFinalityCanOnlyHaveOneMode', + inputs: [ + { + name: 'encodedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + }, + { + type: 'error', + name: 'SafeERC20FailedOperation', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'TokenMaxCapacityExceeded', + inputs: [ + { name: 'capacity', type: 'uint256', internalType: 'uint256' }, + { name: 'requested', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'TokenRateLimitReached', + inputs: [ + { + name: 'minWaitInSeconds', + type: 'uint256', + internalType: 'uint256', + }, + { name: 'available', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'Unauthorized', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'ZeroAddressInvalid', inputs: [] }, + { type: 'error', name: 'ZeroAddressNotAllowed', inputs: [] }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/cross-chain-token.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/cross-chain-token.ts new file mode 100644 index 000000000..e967eebfb --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/cross-chain-token.ts @@ -0,0 +1,661 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v2_0_0/cross_chain_token.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + type: 'constructor', + inputs: [ + { + name: 'args', + type: 'tuple', + internalType: 'struct BaseERC20.ConstructorParams', + components: [ + { name: 'name', type: 'string', internalType: 'string' }, + { name: 'symbol', type: 'string', internalType: 'string' }, + { + name: 'maxSupply', + type: 'uint256', + internalType: 'uint256', + }, + { name: 'preMint', type: 'uint256', internalType: 'uint256' }, + { + name: 'preMintRecipient', + type: 'address', + internalType: 'address', + }, + { name: 'decimals', type: 'uint8', internalType: 'uint8' }, + { + name: 'ccipAdmin', + type: 'address', + internalType: 'address', + }, + ], + }, + { + name: 'burnMintRoleAdmin', + type: 'address', + internalType: 'address', + }, + { name: 'owner', type: 'address', internalType: 'address' }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'BURNER_ROLE', + inputs: [], + outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'BURN_MINT_ADMIN_ROLE', + inputs: [], + outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'DEFAULT_ADMIN_ROLE', + inputs: [], + outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'MINTER_ROLE', + inputs: [], + outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'acceptDefaultAdminTransfer', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'allowance', + inputs: [ + { name: 'owner', type: 'address', internalType: 'address' }, + { name: 'spender', type: 'address', internalType: 'address' }, + ], + outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'approve', + inputs: [ + { name: 'spender', type: 'address', internalType: 'address' }, + { name: 'value', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'balanceOf', + inputs: [{ name: 'account', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'beginDefaultAdminTransfer', + inputs: [{ name: 'newAdmin', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'burn', + inputs: [{ name: 'amount', type: 'uint256', internalType: 'uint256' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'burn', + inputs: [ + { name: 'account', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'burnFrom', + inputs: [ + { name: 'account', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'cancelDefaultAdminTransfer', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'changeDefaultAdminDelay', + inputs: [{ name: 'newDelay', type: 'uint48', internalType: 'uint48' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'decimals', + inputs: [], + outputs: [{ name: '_decimals', type: 'uint8', internalType: 'uint8' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'defaultAdmin', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'defaultAdminDelay', + inputs: [], + outputs: [{ name: '', type: 'uint48', internalType: 'uint48' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'defaultAdminDelayIncreaseWait', + inputs: [], + outputs: [{ name: '', type: 'uint48', internalType: 'uint48' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCCIPAdmin', + inputs: [], + outputs: [{ name: 'ccipAdmin', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRoleAdmin', + inputs: [{ name: 'role', type: 'bytes32', internalType: 'bytes32' }], + outputs: [{ name: '', type: 'bytes32', internalType: 'bytes32' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'grantMintAndBurnRoles', + inputs: [ + { + name: 'burnAndMinter', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'grantRole', + inputs: [ + { name: 'role', type: 'bytes32', internalType: 'bytes32' }, + { name: 'account', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'hasRole', + inputs: [ + { name: 'role', type: 'bytes32', internalType: 'bytes32' }, + { name: 'account', type: 'address', internalType: 'address' }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'maxSupply', + inputs: [], + outputs: [{ name: '_maxSupply', type: 'uint256', internalType: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'mint', + inputs: [ + { name: 'account', type: 'address', internalType: 'address' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'name', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'pendingDefaultAdmin', + inputs: [], + outputs: [ + { name: 'newAdmin', type: 'address', internalType: 'address' }, + { name: 'schedule', type: 'uint48', internalType: 'uint48' }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'pendingDefaultAdminDelay', + inputs: [], + outputs: [ + { name: 'newDelay', type: 'uint48', internalType: 'uint48' }, + { name: 'schedule', type: 'uint48', internalType: 'uint48' }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'renounceRole', + inputs: [ + { name: 'role', type: 'bytes32', internalType: 'bytes32' }, + { name: 'account', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'revokeRole', + inputs: [ + { name: 'role', type: 'bytes32', internalType: 'bytes32' }, + { name: 'account', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'rollbackDefaultAdminDelay', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setCCIPAdmin', + inputs: [{ name: 'newAdmin', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'supportsInterface', + inputs: [{ name: 'interfaceId', type: 'bytes4', internalType: 'bytes4' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'symbol', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'totalSupply', + inputs: [], + outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'transfer', + inputs: [ + { name: 'to', type: 'address', internalType: 'address' }, + { name: 'value', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'transferFrom', + inputs: [ + { name: 'from', type: 'address', internalType: 'address' }, + { name: 'to', type: 'address', internalType: 'address' }, + { name: 'value', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'typeAndVersion', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'pure', + }, + { + type: 'event', + name: 'Approval', + inputs: [ + { + name: 'owner', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'spender', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'CCIPAdminTransferred', + inputs: [ + { + name: 'previousAdmin', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'newAdmin', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'DefaultAdminDelayChangeCanceled', + inputs: [], + anonymous: false, + }, + { + type: 'event', + name: 'DefaultAdminDelayChangeScheduled', + inputs: [ + { + name: 'newDelay', + type: 'uint48', + indexed: false, + internalType: 'uint48', + }, + { + name: 'effectSchedule', + type: 'uint48', + indexed: false, + internalType: 'uint48', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'DefaultAdminTransferCanceled', + inputs: [], + anonymous: false, + }, + { + type: 'event', + name: 'DefaultAdminTransferScheduled', + inputs: [ + { + name: 'newAdmin', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'acceptSchedule', + type: 'uint48', + indexed: false, + internalType: 'uint48', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RoleAdminChanged', + inputs: [ + { + name: 'role', + type: 'bytes32', + indexed: true, + internalType: 'bytes32', + }, + { + name: 'previousAdminRole', + type: 'bytes32', + indexed: true, + internalType: 'bytes32', + }, + { + name: 'newAdminRole', + type: 'bytes32', + indexed: true, + internalType: 'bytes32', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RoleGranted', + inputs: [ + { + name: 'role', + type: 'bytes32', + indexed: true, + internalType: 'bytes32', + }, + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'sender', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RoleRevoked', + inputs: [ + { + name: 'role', + type: 'bytes32', + indexed: true, + internalType: 'bytes32', + }, + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'sender', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Transfer', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'value', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { type: 'error', name: 'AccessControlBadConfirmation', inputs: [] }, + { + type: 'error', + name: 'AccessControlEnforcedDefaultAdminDelay', + inputs: [{ name: 'schedule', type: 'uint48', internalType: 'uint48' }], + }, + { + type: 'error', + name: 'AccessControlEnforcedDefaultAdminRules', + inputs: [], + }, + { + type: 'error', + name: 'AccessControlInvalidDefaultAdmin', + inputs: [ + { + name: 'defaultAdmin', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'AccessControlUnauthorizedAccount', + inputs: [ + { name: 'account', type: 'address', internalType: 'address' }, + { name: 'neededRole', type: 'bytes32', internalType: 'bytes32' }, + ], + }, + { type: 'error', name: 'CannotRenounceCCIPAdmin', inputs: [] }, + { + type: 'error', + name: 'ERC20InsufficientAllowance', + inputs: [ + { name: 'spender', type: 'address', internalType: 'address' }, + { name: 'allowance', type: 'uint256', internalType: 'uint256' }, + { name: 'needed', type: 'uint256', internalType: 'uint256' }, + ], + }, + { + type: 'error', + name: 'ERC20InsufficientBalance', + inputs: [ + { name: 'sender', type: 'address', internalType: 'address' }, + { name: 'balance', type: 'uint256', internalType: 'uint256' }, + { name: 'needed', type: 'uint256', internalType: 'uint256' }, + ], + }, + { + type: 'error', + name: 'ERC20InvalidApprover', + inputs: [{ name: 'approver', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'ERC20InvalidReceiver', + inputs: [{ name: 'receiver', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'ERC20InvalidSender', + inputs: [{ name: 'sender', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'ERC20InvalidSpender', + inputs: [{ name: 'spender', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'MaxSupplyExceeded', + inputs: [ + { + name: 'supplyAfterMint', + type: 'uint256', + internalType: 'uint256', + }, + { name: 'maxSupply', type: 'uint256', internalType: 'uint256' }, + ], + }, + { type: 'error', name: 'OnlyCCIPAdmin', inputs: [] }, + { type: 'error', name: 'PreMintAddressNotSet', inputs: [] }, + { + type: 'error', + name: 'PreMintRecipientSetWithZeroPreMint', + inputs: [ + { + name: 'preMintRecipient', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'SafeCastOverflowedUintDowncast', + inputs: [ + { name: 'bits', type: 'uint8', internalType: 'uint8' }, + { name: 'value', type: 'uint256', internalType: 'uint256' }, + ], + }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/erc20-lockbox.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/erc20-lockbox.ts new file mode 100644 index 000000000..b498bff89 --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/erc20-lockbox.ts @@ -0,0 +1,254 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v2_0_0/erc20_lock_box.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + type: 'constructor', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'acceptOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyAuthorizedCallerUpdates', + inputs: [ + { + name: 'authorizedCallerArgs', + type: 'tuple', + internalType: 'struct AuthorizedCallers.AuthorizedCallerArgs', + components: [ + { + name: 'addedCallers', + type: 'address[]', + internalType: 'address[]', + }, + { + name: 'removedCallers', + type: 'address[]', + internalType: 'address[]', + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'deposit', + inputs: [ + { name: 'token', type: 'address', internalType: 'address' }, + { name: '', type: 'uint64', internalType: 'uint64' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'getAllAuthorizedCallers', + inputs: [], + outputs: [{ name: '', type: 'address[]', internalType: 'address[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getToken', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'contract IERC20' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isTokenSupported', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [{ name: 'to', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'typeAndVersion', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'withdraw', + inputs: [ + { name: 'token', type: 'address', internalType: 'address' }, + { name: '', type: 'uint64', internalType: 'uint64' }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + { name: 'recipient', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'event', + name: 'AuthorizedCallerAdded', + inputs: [ + { + name: 'caller', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'AuthorizedCallerRemoved', + inputs: [ + { + name: 'caller', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Deposit', + inputs: [ + { + name: 'token', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'depositor', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferRequested', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'Withdrawal', + inputs: [ + { + name: 'token', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'recipient', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { type: 'error', name: 'CannotTransferToSelf', inputs: [] }, + { + type: 'error', + name: 'InsufficientBalance', + inputs: [ + { name: 'requested', type: 'uint256', internalType: 'uint256' }, + { name: 'available', type: 'uint256', internalType: 'uint256' }, + ], + }, + { type: 'error', name: 'MustBeProposedOwner', inputs: [] }, + { type: 'error', name: 'OnlyCallableByOwner', inputs: [] }, + { type: 'error', name: 'OwnerCannotBeZero', inputs: [] }, + { type: 'error', name: 'RecipientCannotBeZeroAddress', inputs: [] }, + { + type: 'error', + name: 'SafeERC20FailedOperation', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'TokenAmountCannotBeZero', inputs: [] }, + { + type: 'error', + name: 'UnauthorizedCaller', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'UnsupportedToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'ZeroAddressNotAllowed', inputs: [] }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/lock-release-token-pool.ts b/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/lock-release-token-pool.ts new file mode 100644 index 000000000..1b888ccd2 --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/abi/V2_0_0/lock-release-token-pool.ts @@ -0,0 +1,1673 @@ +export default [ + // generate: + // (() => { + // const abi = require('@chainlink/contracts-ccip/abi/v2_0_0/lock_release_token_pool.json') + // return require('util').inspect(Array.isArray(abi) ? abi : abi.abi, { depth: 99 }).split('\n').slice(1, -1) + // })() + { + type: 'constructor', + inputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IERC20', + }, + { + name: 'localTokenDecimals', + type: 'uint8', + internalType: 'uint8', + }, + { + name: 'advancedPoolHooks', + type: 'address', + internalType: 'address', + }, + { name: 'rmnProxy', type: 'address', internalType: 'address' }, + { name: 'router', type: 'address', internalType: 'address' }, + { name: 'lockBox', type: 'address', internalType: 'address' }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'acceptOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'addRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyChainUpdates', + inputs: [ + { + name: 'remoteChainSelectorsToRemove', + type: 'uint64[]', + internalType: 'uint64[]', + }, + { + name: 'chainsToAdd', + type: 'tuple[]', + internalType: 'struct TokenPool.ChainUpdate[]', + components: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddresses', + type: 'bytes[]', + internalType: 'bytes[]', + }, + { + name: 'remoteTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'applyTokenTransferFeeConfigUpdates', + inputs: [ + { + name: 'tokenTransferFeeConfigArgs', + type: 'tuple[]', + internalType: 'struct TokenPool.TokenTransferFeeConfigArgs[]', + components: [ + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'tokenTransferFeeConfig', + type: 'tuple', + internalType: 'struct IPoolV2.TokenTransferFeeConfig', + components: [ + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'fastFinalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { + name: 'fastFinalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + }, + ], + }, + { + name: 'disableTokenTransferFeeConfigs', + type: 'uint64[]', + internalType: 'uint64[]', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'getAdvancedPoolHooks', + inputs: [], + outputs: [ + { + name: 'advancedPoolHook', + type: 'address', + internalType: 'contract IAdvancedPoolHooks', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getAllowedFinalityConfig', + inputs: [], + outputs: [ + { + name: 'allowedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getCurrentRateLimiterState', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: 'fastFinality', type: 'bool', internalType: 'bool' }, + ], + outputs: [ + { + name: 'outboundRateLimiterState', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterState', + type: 'tuple', + internalType: 'struct RateLimiter.TokenBucket', + components: [ + { name: 'tokens', type: 'uint128', internalType: 'uint128' }, + { + name: 'lastUpdated', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getDynamicConfig', + inputs: [], + outputs: [ + { name: 'router', type: 'address', internalType: 'address' }, + { + name: 'rateLimitAdmin', + type: 'address', + internalType: 'address', + }, + { name: 'feeAdmin', type: 'address', internalType: 'address' }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getFee', + inputs: [ + { name: '', type: 'address', internalType: 'address' }, + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: '', type: 'uint256', internalType: 'uint256' }, + { name: '', type: 'address', internalType: 'address' }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + { name: '', type: 'bytes', internalType: 'bytes' }, + ], + outputs: [ + { name: 'feeUSDCents', type: 'uint256', internalType: 'uint256' }, + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { name: 'tokenFeeBps', type: 'uint16', internalType: 'uint16' }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getLockBox', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemotePools', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes[]', internalType: 'bytes[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRemoteToken', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bytes', internalType: 'bytes' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRequiredCCVs', + inputs: [ + { name: 'localToken', type: 'address', internalType: 'address' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'sourceDenominatedAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + { name: 'extraData', type: 'bytes', internalType: 'bytes' }, + { + name: 'direction', + type: 'uint8', + internalType: 'enum IPoolV2.MessageDirection', + }, + ], + outputs: [ + { + name: 'requiredCCVs', + type: 'address[]', + internalType: 'address[]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRmnProxy', + inputs: [], + outputs: [{ name: 'rmnProxy', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getSupportedChains', + inputs: [], + outputs: [{ name: '', type: 'uint64[]', internalType: 'uint64[]' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getToken', + inputs: [], + outputs: [ + { + name: 'token', + type: 'address', + internalType: 'contract IERC20', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getTokenDecimals', + inputs: [], + outputs: [{ name: 'decimals', type: 'uint8', internalType: 'uint8' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getTokenTransferFeeConfig', + inputs: [ + { name: '', type: 'address', internalType: 'address' }, + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: '', type: 'bytes4', internalType: 'bytes4' }, + { name: '', type: 'bytes', internalType: 'bytes' }, + ], + outputs: [ + { + name: 'feeConfig', + type: 'tuple', + internalType: 'struct IPoolV2.TokenTransferFeeConfig', + components: [ + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'fastFinalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { + name: 'fastFinalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isSupportedToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'lockOrBurn', + inputs: [ + { + name: 'lockOrBurnIn', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnInV1', + components: [ + { name: 'receiver', type: 'bytes', internalType: 'bytes' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'originalSender', + type: 'address', + internalType: 'address', + }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + ], + }, + ], + outputs: [ + { + name: 'lockOrBurnOutV1', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnOutV1', + components: [ + { + name: 'destTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'destPoolData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'lockOrBurn', + inputs: [ + { + name: 'lockOrBurnIn', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnInV1', + components: [ + { name: 'receiver', type: 'bytes', internalType: 'bytes' }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'originalSender', + type: 'address', + internalType: 'address', + }, + { name: 'amount', type: 'uint256', internalType: 'uint256' }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + ], + }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + { name: 'tokenArgs', type: 'bytes', internalType: 'bytes' }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.LockOrBurnOutV1', + components: [ + { + name: 'destTokenAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'destPoolData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + name: 'destTokenAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'releaseOrMint', + inputs: [ + { + name: 'releaseOrMintIn', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintInV1', + components: [ + { + name: 'originalSender', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'receiver', + type: 'address', + internalType: 'address', + }, + { + name: 'sourceDenominatedAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'sourcePoolData', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'offchainTokenData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + name: 'requestedFinalityConfig', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintOutV1', + components: [ + { + name: 'destinationAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'releaseOrMint', + inputs: [ + { + name: 'releaseOrMintIn', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintInV1', + components: [ + { + name: 'originalSender', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'receiver', + type: 'address', + internalType: 'address', + }, + { + name: 'sourceDenominatedAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'localToken', + type: 'address', + internalType: 'address', + }, + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'sourcePoolData', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'offchainTokenData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct Pool.ReleaseOrMintOutV1', + components: [ + { + name: 'destinationAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'removeRemotePool', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setAllowedFinalityConfig', + inputs: [ + { + name: 'allowedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setDynamicConfig', + inputs: [ + { name: 'router', type: 'address', internalType: 'address' }, + { + name: 'rateLimitAdmin', + type: 'address', + internalType: 'address', + }, + { name: 'feeAdmin', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setRateLimitConfig', + inputs: [ + { + name: 'rateLimitConfigArgs', + type: 'tuple[]', + internalType: 'struct TokenPool.RateLimitConfigArgs[]', + components: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { name: 'fastFinality', type: 'bool', internalType: 'bool' }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'rate', + type: 'uint128', + internalType: 'uint128', + }, + ], + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'supportsInterface', + inputs: [{ name: 'interfaceId', type: 'bytes4', internalType: 'bytes4' }], + outputs: [{ name: '', type: 'bool', internalType: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [{ name: 'to', type: 'address', internalType: 'address' }], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'typeAndVersion', + inputs: [], + outputs: [{ name: '', type: 'string', internalType: 'string' }], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'updateAdvancedPoolHooks', + inputs: [ + { + name: 'newHook', + type: 'address', + internalType: 'contract IAdvancedPoolHooks', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'withdrawFeeTokens', + inputs: [ + { + name: 'feeTokens', + type: 'address[]', + internalType: 'address[]', + }, + { name: 'recipient', type: 'address', internalType: 'address' }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'event', + name: 'AdvancedPoolHooksUpdated', + inputs: [ + { + name: 'oldHook', + type: 'address', + indexed: false, + internalType: 'contract IAdvancedPoolHooks', + }, + { + name: 'newHook', + type: 'address', + indexed: false, + internalType: 'contract IAdvancedPoolHooks', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + { + name: 'remoteToken', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChainRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: false, + internalType: 'uint64', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'DynamicConfigSet', + inputs: [ + { + name: 'router', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'rateLimitAdmin', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'feeAdmin', + type: 'address', + indexed: false, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FastFinalityInboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FastFinalityOutboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FeeTokenWithdrawn', + inputs: [ + { + name: 'receiver', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'feeToken', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'FinalityConfigSet', + inputs: [ + { + name: 'allowedFinality', + type: 'bytes4', + indexed: false, + internalType: 'bytes4', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'InboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'LockedOrBurned', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OutboundRateLimitConsumed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferRequested', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'from', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'to', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RateLimitConfigured', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'fastFinality', + type: 'bool', + indexed: false, + internalType: 'bool', + }, + { + name: 'outboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + { + name: 'inboundRateLimiterConfig', + type: 'tuple', + indexed: false, + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ReleasedOrMinted', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'token', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'sender', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'recipient', + type: 'address', + indexed: false, + internalType: 'address', + }, + { + name: 'amount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemotePoolRemoved', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + indexed: false, + internalType: 'bytes', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'TokenTransferFeeConfigDeleted', + inputs: [ + { + name: 'destChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'TokenTransferFeeConfigUpdated', + inputs: [ + { + name: 'destChainSelector', + type: 'uint64', + indexed: true, + internalType: 'uint64', + }, + { + name: 'tokenTransferFeeConfig', + type: 'tuple', + indexed: false, + internalType: 'struct IPoolV2.TokenTransferFeeConfig', + components: [ + { + name: 'destGasOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'destBytesOverhead', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'fastFinalityFeeUSDCents', + type: 'uint32', + internalType: 'uint32', + }, + { + name: 'finalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { + name: 'fastFinalityTransferFeeBps', + type: 'uint16', + internalType: 'uint16', + }, + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + ], + }, + ], + anonymous: false, + }, + { type: 'error', name: 'BucketOverfilled', inputs: [] }, + { + type: 'error', + name: 'CallerIsNotARampOnRouter', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'CallerIsNotOwnerOrFeeAdmin', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'CannotTransferToSelf', inputs: [] }, + { + type: 'error', + name: 'ChainAlreadyExists', + inputs: [{ name: 'chainSelector', type: 'uint64', internalType: 'uint64' }], + }, + { + type: 'error', + name: 'ChainNotAllowed', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'CursedByRMN', inputs: [] }, + { + type: 'error', + name: 'DisabledNonZeroRateLimit', + inputs: [ + { + name: 'config', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { + type: 'error', + name: 'InvalidDecimalArgs', + inputs: [ + { name: 'expected', type: 'uint8', internalType: 'uint8' }, + { name: 'actual', type: 'uint8', internalType: 'uint8' }, + ], + }, + { + type: 'error', + name: 'InvalidRateLimitRate', + inputs: [ + { + name: 'rateLimiterConfig', + type: 'tuple', + internalType: 'struct RateLimiter.Config', + components: [ + { name: 'isEnabled', type: 'bool', internalType: 'bool' }, + { + name: 'capacity', + type: 'uint128', + internalType: 'uint128', + }, + { name: 'rate', type: 'uint128', internalType: 'uint128' }, + ], + }, + ], + }, + { + type: 'error', + name: 'InvalidRemoteChainDecimals', + inputs: [{ name: 'sourcePoolData', type: 'bytes', internalType: 'bytes' }], + }, + { + type: 'error', + name: 'InvalidRemotePoolForChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidRequestedFinality', + inputs: [ + { + name: 'requestedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + { + name: 'allowedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + }, + { + type: 'error', + name: 'InvalidSourcePoolAddress', + inputs: [ + { + name: 'sourcePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'InvalidToken', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'InvalidTokenTransferFeeConfig', + inputs: [ + { + name: 'destChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { + type: 'error', + name: 'InvalidTransferFeeBps', + inputs: [{ name: 'bps', type: 'uint256', internalType: 'uint256' }], + }, + { type: 'error', name: 'MustBeProposedOwner', inputs: [] }, + { + type: 'error', + name: 'NonExistentChain', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + ], + }, + { type: 'error', name: 'OnlyCallableByOwner', inputs: [] }, + { + type: 'error', + name: 'OverflowDetected', + inputs: [ + { name: 'remoteDecimals', type: 'uint8', internalType: 'uint8' }, + { name: 'localDecimals', type: 'uint8', internalType: 'uint8' }, + { + name: 'remoteAmount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { type: 'error', name: 'OwnerCannotBeZero', inputs: [] }, + { + type: 'error', + name: 'PoolAlreadyAdded', + inputs: [ + { + name: 'remoteChainSelector', + type: 'uint64', + internalType: 'uint64', + }, + { + name: 'remotePoolAddress', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + type: 'error', + name: 'RequestedFinalityCanOnlyHaveOneMode', + inputs: [ + { + name: 'encodedFinality', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + }, + { + type: 'error', + name: 'SafeERC20FailedOperation', + inputs: [{ name: 'token', type: 'address', internalType: 'address' }], + }, + { + type: 'error', + name: 'TokenMaxCapacityExceeded', + inputs: [ + { name: 'capacity', type: 'uint256', internalType: 'uint256' }, + { name: 'requested', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'TokenRateLimitReached', + inputs: [ + { + name: 'minWaitInSeconds', + type: 'uint256', + internalType: 'uint256', + }, + { name: 'available', type: 'uint256', internalType: 'uint256' }, + { + name: 'tokenAddress', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'Unauthorized', + inputs: [{ name: 'caller', type: 'address', internalType: 'address' }], + }, + { type: 'error', name: 'ZeroAddressInvalid', inputs: [] }, + { type: 'error', name: 'ZeroAddressNotAllowed', inputs: [] }, + // generate:end +] as const diff --git a/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/burn-from-mint-token-pool.ts b/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/burn-from-mint-token-pool.ts new file mode 100644 index 000000000..48cd4ad75 --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/burn-from-mint-token-pool.ts @@ -0,0 +1,4 @@ +export default // generate: +// `'${require('fs').readFileSync(require('module').createRequire(`${process.cwd()}/package.json`).resolve('@chainlink/contracts-ccip/bytecode/v2_0_0/burn_from_mint_token_pool.bin'), 'utf8').trim()}' as const` +'0x60e080604052346102aa5760a081615ee1803803809161001f82856102fb565b8339810103126102aa5780516001600160a01b03811691908290036102aa5761004a60208201610334565b61005660408301610342565b9061006f608061006860608601610342565b9401610342565b9233156102ea57600180546001600160a01b03191633179055841580156102d9575b80156102c8575b6102b7578460805260c052308403610220575b60a052600380546001600160a01b039283166001600160a01b0319918216179091556002805493909216921691909117905560405163095ea7b360e01b60208083019182523060248401526000196044808501919091528352906000906101136064856102fb565b83519082865af16000513d82610204575b5050156101bf575b604051615b2390816103be823960805181818161023e01528181610491015281816122700152818161244801528181612ab501528181612cb0015281816131a20152818161374f01526137a9015260a05181818161361501528181614928015281816149720152614ebc015260c0518181816102d9015281816113f50152818161230a01528181612b50015261323d0152f35b6101fd916101f860405163095ea7b360e01b602082015230602482015260006044820152604481526101f26064826102fb565b82610356565b610356565b388061012c565b9091506102185750813b15155b3880610124565b600114610211565b60405163313ce56760e01b8152602081600481885afa60009181610276575b5061024b575b506100ab565b60ff1660ff821681810361025f5750610245565b6332ad3e0760e11b60005260045260245260446000fd5b9091506020813d6020116102af575b81610292602093836102fb565b810103126102aa576102a390610334565b903861023f565b600080fd5b3d9150610285565b630a64406560e11b60005260046000fd5b506001600160a01b03811615610098565b506001600160a01b03841615610091565b639b15e16f60e01b60005260046000fd5b601f909101601f19168101906001600160401b0382119082101761031e57604052565b634e487b7160e01b600052604160045260246000fd5b519060ff821682036102aa57565b51906001600160a01b03821682036102aa57565b906000602091828151910182855af1156103b1576000513d6103a857506001600160a01b0381163b155b6103875750565b635274afe760e01b60009081526001600160a01b0391909116600452602490fd5b60011415610380565b6040513d6000823e3d90fdfe608080604052600436101561001357600080fd5b600090813560e01c90816301ffc9a7146139ca5750806306b859ef146138e5578063181f5a77146138845780631826b1e7146137cd57806321df0da71461377c578063240028e8146137185780632422ac451461363957806324f65ee7146135fb5780632cab0fb61461310757806337a3210d146130d35780633907753714612a0a5780634c5ef0ed146129c357806362ddd3c41461293c5780637437ff9f146128ee57806379ba5097146128275780638926f54f146127e15780638da5cb5b146127ad5780639a4575b9146121f7578063a42a7b8b14612090578063acfecf9114611f98578063ae39a25714611e0d578063b6cfa3b714611d52578063b794658014611d1a578063bfeffd3f14611c6e578063c4bffe2b14611b43578063c7230a601461189d578063dc04fa1f14611419578063dc0bd971146113c8578063dcbd41bc146111c4578063e8a1da1714610ae8578063ea6396db146109aa578063ec6ae7a714610967578063f2fde38b146108985763fbc801a71461019757600080fd5b346105db5760606003193601126105db576004359067ffffffffffffffff82116105db578160040160a060031984360301126105e9576101d5613afc565b9060443567ffffffffffffffff811161070f57906101fa610217923690600401613c27565b92906102046145e4565b5061020f8584615120565b933691613da1565b92608486019361022685614571565b73ffffffffffffffffffffffffffffffffffffffff807f00000000000000000000000000000000000000000000000000000000000000001691160361084e57602487019677ffffffffffffffff0000000000000000000000000000000061028c89614592565b60801b16604051907f2cbc26bb000000000000000000000000000000000000000000000000000000008252600482015260208160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa9081156107c157889161081f575b506107f75767ffffffffffffffff61032089614592565b16610338816000526007602052604060002054151590565b156107cc57602073ffffffffffffffffffffffffffffffffffffffff60025416916024604051809481937fa8d87a3b00000000000000000000000000000000000000000000000000000000835260048301525afa80156107c1578890610770575b73ffffffffffffffffffffffffffffffffffffffff9150163303610744576064810135936103c78686613f88565b7fffffffff00000000000000000000000000000000000000000000000000000000851694851561072257610423907fffffffff0000000000000000000000000000000000000000000000000000000060025460401b1690614a7c565b61043f816104308a614571565b6104398d614592565b90615408565b73ffffffffffffffffffffffffffffffffffffffff6003541693846105ed575b5050505050509061046f91613f88565b9161047984614592565b5073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016803b156105e9576040517f79cc6790000000000000000000000000000000000000000000000000000000008152306004820152602481018590529082908290604490829084905af180156105de576105c6575b6105bc8461058b61058688877ff33bc26b4413b0e7f19f1ea739fdf99098c0061f1f87d954b11f5293fad9ae1067ffffffffffffffff61054c61054685614592565b93614571565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252336020830152810188905292169180606081015b0390a2614592565b614755565b90610594614eb5565b604051926105a184613d0c565b83526020830152604051928392604084526040840190613e69565b9060208301520390f35b6105d1828092613d60565b6105db5780610504565b80fd5b6040513d84823e3d90fd5b5080fd5b843b1561071e578994928b9694928692604051988997889687957fa8027c0f00000000000000000000000000000000000000000000000000000000875260048701608090528061063c91615372565b6084880160a0905261012488019061065392613fb6565b9261065d90613c12565b67ffffffffffffffff1660a487015260440161067890613bc3565b73ffffffffffffffffffffffffffffffffffffffff1660c48601528d8c60e48701526106a390613bc3565b73ffffffffffffffffffffffffffffffffffffffff1661010486015260248501528381036003190160448501526106d991613c55565b90606483015203925af18015610713579085916106fa575b8080808061045f565b8161070491613d60565b61070f5783386106f1565b8380fd5b6040513d87823e3d90fd5b8980fd5b5061073f816107308a614571565b6107398d614592565b906153c2565b61043f565b6024877f728fe07b00000000000000000000000000000000000000000000000000000000815233600452fd5b506020813d6020116107b9575b8161078a60209383613d60565b810103126107b5576107b073ffffffffffffffffffffffffffffffffffffffff91613f95565b610399565b8780fd5b3d915061077d565b6040513d8a823e3d90fd5b7fa9902c7e000000000000000000000000000000000000000000000000000000008852600452602487fd5b6004877f53ad11d8000000000000000000000000000000000000000000000000000000008152fd5b610841915060203d602011610847575b6108398183613d60565b810190614be8565b38610309565b503d61082f565b60248673ffffffffffffffffffffffffffffffffffffffff61086f88614571565b7f961c9a4f00000000000000000000000000000000000000000000000000000000835216600452fd5b50346105db5760206003193601126105db5773ffffffffffffffffffffffffffffffffffffffff6108c7613b5a565b6108cf614c00565b1633811461093f57807fffffffffffffffffffffffff000000000000000000000000000000000000000083541617825573ffffffffffffffffffffffffffffffffffffffff600154167fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12788380a380f35b6004827fdad89dca000000000000000000000000000000000000000000000000000000008152fd5b50346105db57806003193601126105db5760207fffffffff0000000000000000000000000000000000000000000000000000000060025460401b16604051908152f35b50346105db5760806003193601126105db576109c4613b5a565b506109cd613be4565b6109d5613b2b565b5060643567ffffffffffffffff8111610ae4579167ffffffffffffffff604092610a0560e0953690600401613c27565b50508260c08551610a1581613d44565b82815282602082015282878201528260608201528260808201528260a08201520152168152600b6020522060405190610a4d82613d44565b5461ffff818163ffffffff82169485815263ffffffff60208201818560201c1681528160408401818760401c168152816060860193818960601c16855260ff60c060808901988a8c60801c168a528a60a082019c60901c168c52019b60a01c1615158b526040519b8c52511660208b0152511660408901525116606087015251166080850152511660a083015251151560c0820152f35b8280fd5b50346105db5760406003193601126105db5760043567ffffffffffffffff81116105e957610b1a903690600401613e93565b9060243567ffffffffffffffff811161070f5790610b3d84923690600401613e93565b939091610b48614c00565b83905b8282106110055750505081927ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee182360301935b81811015611001578060051b83013585811215610ffd57830161012081360312610ffd5760405194610baf86613d28565b610bb882613c12565b8652602082013567ffffffffffffffff81116105e95782019436601f870112156105e957853595610be887613ef5565b96610bf66040519889613d60565b80885260208089019160051b83010190368211610ffd5760208301905b828210610fca575050505060208701958652604083013567ffffffffffffffff8111610ae457610c469036908501613e06565b9160408801928352610c70610c5e3660608701614801565b9460608a0195865260c0369101614801565b956080890196875283515115610fa257610c9467ffffffffffffffff8a51166157a5565b15610f6b5767ffffffffffffffff8951168252600860205260408220610cbb865182614ef0565b610cc9885160028301614ef0565b6004855191019080519067ffffffffffffffff8211610f3e57610cec8354614640565b601f8111610f03575b50602090601f8311600114610e6457610d439291869183610e59575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b90555b815b88518051821015610d7d5790610d77600192610d708367ffffffffffffffff8f5116926145fd565b5190614c4b565b01610d48565b5050977f8d340f17e19058004c20453540862a9c62778504476f6756755cb33bcd6c38c2939199975095610e4b67ffffffffffffffff6001979694985116925193519151610e17610de260405196879687526101006020880152610100870190613c55565b9360408601906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565b60a08401906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565b0390a1019392909193610b7e565b015190508e80610d11565b83865281862091907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08416875b818110610eeb5750908460019594939210610eb4575b505050811b019055610d46565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558d8080610ea7565b92936020600181928786015181550195019301610e91565b610f2e9084875260208720601f850160051c81019160208610610f34575b601f0160051c019061489d565b8d610cf5565b9091508190610f21565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b60248267ffffffffffffffff8b51167f1d5ad3c5000000000000000000000000000000000000000000000000000000008252600452fd5b6004827f14c880ca000000000000000000000000000000000000000000000000000000008152fd5b813567ffffffffffffffff8111610ff957602091610fee8392833691890101613e06565b815201910190610c13565b8680fd5b8480fd5b8380f35b9267ffffffffffffffff6110276110228486889a9699979a6147d4565b614592565b1691611032836154db565b1561119857828452600860205261104e60056040862001615478565b94845b865181101561108757600190858752600860205261108060056040892001611079838b6145fd565b5190615671565b5001611051565b50939692909450949094808752600860205260056040882088815588600182015588600282015588600382015588600482016110c38154614640565b80611157575b5050500180549088815581611139575b5050907f5204aec90a3c794d8e90fded8b46ae9c7c552803e7e832e0c1d358396d8599166020836001948a52600482528985604082208281550155808a52600582528985604082208281550155604051908152a101909194939294610b4b565b885260208820908101905b818110156110d957888155600101611144565b601f811160011461116d5750555b888a806110c9565b8183526020832061118891601f01861c81019060010161489d565b8082528160208120915555611165565b602484847f1e670e4b000000000000000000000000000000000000000000000000000000008252600452fd5b50346105db5760206003193601126105db5760043567ffffffffffffffff81116105e9576111f6903690600401613ec4565b73ffffffffffffffffffffffffffffffffffffffff600a5416331415806113a6575b61137a57825b818110611229578380f35b611234818385614777565b67ffffffffffffffff61124682614592565b169061125f826000526007602052604060002054151590565b1561134e57907f41f7c8f7cfdad9350aa495e6c54cbbf750a07ab38a9098aed1256e30dd1682bb60e08361130e6112e8602060019897018b6112a082614787565b156113155787905260046020526112c760408d206112c13660408801614801565b90614ef0565b868c5260056020526112e360408d206112c13660a08801614801565b614787565b9160405192151583526113016020840160408301614859565b60a0608084019101614859565ba20161121e565b60026040828a6112e3945260086020526113378282206112c136858c01614801565b8a8152600860205220016112c13660a08801614801565b602486837f1e670e4b000000000000000000000000000000000000000000000000000000008252600452fd5b6024837f8e4a23d600000000000000000000000000000000000000000000000000000000815233600452fd5b5073ffffffffffffffffffffffffffffffffffffffff60015416331415611218565b50346105db57806003193601126105db57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50346105db5760406003193601126105db5760043567ffffffffffffffff81116105e95761144b903690600401613ec4565b60243567ffffffffffffffff811161070f5761146b903690600401613e93565b919092611476614c00565b845b8281106114e257505050825b81811061148f578380f35b8067ffffffffffffffff6114a961102260019486886147d4565b16808652600b6020528560408120557f5479bbc0288b7eaeaf2ace0943b88016cc648964fcd42919a86fd93b15fdbee88680a201611484565b67ffffffffffffffff6114f9611022838686614777565b16611511816000526007602052604060002054151590565b1561187257611521828585614777565b602081019060e081019061153482614787565b156118465760a0810161271061ffff61154c83614794565b1610156118375760c082019161271061ffff61156785614794565b1610156117ff5763ffffffff61157c866147a3565b16156117d357858c52600b60205260408c20611597866147a3565b63ffffffff169080549060408401916115af836147a3565b60201b67ffffffff00000000169360608601946115cb866147a3565b60401b6bffffffff00000000000000001696608001966115ea886147a3565b60601b6fffffffff00000000000000000000000016916116098a614794565b60801b71ffff00000000000000000000000000000000169361162a8c614794565b60901b73ffff00000000000000000000000000000000000016957fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016177fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff16177fffffffffffffffffffffffffffff0000ffffffffffffffffffffffffffffffff16177fffffffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffff1617171781556116dd87614787565b81547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690151560a01b74ff0000000000000000000000000000000000000000161790556040519661172e906147b4565b63ffffffff16875261173f906147b4565b63ffffffff166020870152611753906147b4565b63ffffffff166040860152611767906147b4565b63ffffffff16606085015261177b906147c5565b61ffff16608084015261178d906147c5565b61ffff1660a083015261179f90613cb4565b151560c082015260e07ffae1e296719dac5269c3886fb5002bb29bf17ae403060c6eb063a55abaaa104191a2600101611478565b60248c877f12332265000000000000000000000000000000000000000000000000000000008252600452fd5b60248c61ffff61180e86614794565b7f95f3517a00000000000000000000000000000000000000000000000000000000835216600452fd5b8a61ffff61180e602493614794565b60248a857f12332265000000000000000000000000000000000000000000000000000000008252600452fd5b7f1e670e4b000000000000000000000000000000000000000000000000000000008752600452602486fd5b50346105db5760406003193601126105db5760043567ffffffffffffffff81116105e9576118cf903690600401613e93565b906118d8613ba0565b9173ffffffffffffffffffffffffffffffffffffffff6001541633141580611b21575b611af55773ffffffffffffffffffffffffffffffffffffffff8316908115611acd57845b81811061192a578580f35b73ffffffffffffffffffffffffffffffffffffffff61195261194d8385886147d4565b614571565b166040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152602081602481855afa9081156107c1578891611a9a575b50806119a7575b505060010161191f565b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000602080830191825273ffffffffffffffffffffffffffffffffffffffff8a16602484015260448084018590528352918a9190611a08606482613d60565b519082865af115611a8f5787513d611a865750813b155b611a5a5790847f508d7d183612c18fc339b42618912b9fa3239f631dd7ec0671f950200a0fa66e602060019594604051908152a3903861199d565b602488837f5274afe7000000000000000000000000000000000000000000000000000000008252600452fd5b60011415611a1f565b6040513d89823e3d90fd5b905060203d8111611ac6575b611ab08183613d60565b602082600092810103126105db57505138611996565b503d611aa6565b6004857f8579befe000000000000000000000000000000000000000000000000000000008152fd5b6024847fcb1afbd700000000000000000000000000000000000000000000000000000000815233600452fd5b5073ffffffffffffffffffffffffffffffffffffffff600c54163314156118fb565b50346105db57806003193601126105db57604051906006548083528260208101600684526020842092845b818110611c55575050611b8392500383613d60565b8151611ba7611b9182613ef5565b91611b9f6040519384613d60565b808352613ef5565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602083019301368437805b8451811015611c06578067ffffffffffffffff611bf3600193886145fd565b5116611bff82866145fd565b5201611bd4565b50925090604051928392602084019060208552518091526040840192915b818110611c32575050500390f35b825167ffffffffffffffff16845285945060209384019390920191600101611c24565b8454835260019485019487945060209093019201611b6e565b50346105db5760206003193601126105db5760043573ffffffffffffffffffffffffffffffffffffffff81168091036105e957611ca9614c00565b7fffffffffffffffffffffffff00000000000000000000000000000000000000006003547fbaff46844acf36d6ee996f489a1a288709c4542bd33cd557770afd267d5812096040805173ffffffffffffffffffffffffffffffffffffffff84168152856020820152a1161760035580f35b50346105db5760206003193601126105db57611d4e611d3a610586613bfb565b604051918291602083526020830190613c55565b0390f35b50346105db5760206003193601126105db577f307cf716eade81675bea3ccb6917b0f91baa2160056765d9a83d76f819caf06a6020611d8f613ac8565b611d97614c00565b6002547fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff77ffffffff0000000000000000000000000000000000000000808460401c16169116176002557fffffffff0000000000000000000000000000000000000000000000000000000060405191168152a180f35b50346105db5760606003193601126105db57611e27613b5a565b90611e30613ba0565b6044359273ffffffffffffffffffffffffffffffffffffffff841680850361070f57611e5a614c00565b73ffffffffffffffffffffffffffffffffffffffff82168015611f705794611f6a917f3f1036e85d016a93254a0b1415844f79b85424959d90ae5ad51ce8f4533fe70195967fffffffffffffffffffffffff0000000000000000000000000000000000000000600254161760025573ffffffffffffffffffffffffffffffffffffffff85167fffffffffffffffffffffffff0000000000000000000000000000000000000000600a541617600a557fffffffffffffffffffffffff0000000000000000000000000000000000000000600c541617600c556040519384938491604091949373ffffffffffffffffffffffffffffffffffffffff809281606087019816865216602085015216910152565b0390a180f35b6004857f14c880ca000000000000000000000000000000000000000000000000000000008152fd5b50346105db5767ffffffffffffffff611fb036613e24565b929091611fbb614c00565b1691611fd4836000526007602052604060002054151590565b1561119857828452600860205261200360056040862001611ff6368486613da1565b6020815191012090615671565b1561204857907f52d00ee4d9bd51b40168f2afc5848837288ce258784ad914278791464b3f4d7691612042604051928392602084526020840191613fb6565b0390a280f35b8261208c836040519384937f74f23c7c0000000000000000000000000000000000000000000000000000000085526004850152604060248501526044840191613fb6565b0390fd5b50346105db5760206003193601126105db5767ffffffffffffffff6120b3613bfb565b16815260086020526120ca60056040832001615478565b80517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061210f6120f983613ef5565b926121076040519485613d60565b808452613ef5565b01835b8181106121e6575050825b82518110156121635780612133600192856145fd565b518552600960205261214760408620614693565b61215182856145fd565b5261215c81846145fd565b500161211d565b81846040519182916020830160208452825180915260408401602060408360051b870101940192905b82821061219b57505050500390f35b919360206121d6827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc060019597998495030186528851613c55565b960192019201859493919261218c565b806060602080938601015201612112565b50346105db5760206003193601126105db5760043567ffffffffffffffff81116105e957806004019060a06003198236030112610ae4576122366145e4565b506040516020936122478583613d60565b808252608483019161225883614571565b73ffffffffffffffffffffffffffffffffffffffff807f00000000000000000000000000000000000000000000000000000000000000001691160361278c57602484019477ffffffffffffffff000000000000000000000000000000006122be87614592565b60801b16604051907f2cbc26bb0000000000000000000000000000000000000000000000000000000082526004820152878160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa90811561271157849161276f575b506127475767ffffffffffffffff61235187614592565b16612369816000526007602052604060002054151590565b1561271c578773ffffffffffffffffffffffffffffffffffffffff60025416916024604051809481937fa8d87a3b00000000000000000000000000000000000000000000000000000000835260048301525afa80156127115784906126c9575b73ffffffffffffffffffffffffffffffffffffffff915016330361269d57606485013594612403866123fa87614571565b6107398a614592565b73ffffffffffffffffffffffffffffffffffffffff600354169182612580575b5050505061243084614592565b5073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016803b156105e9576040517f79cc6790000000000000000000000000000000000000000000000000000000008152306004820152602481018590529082908290604490829084905af180156105de5761256b575b8561253b61058687877ff33bc26b4413b0e7f19f1ea739fdf99098c0061f1f87d954b11f5293fad9ae1067ffffffffffffffff8961057e6125046124fe87614592565b92614571565b6040805173ffffffffffffffffffffffffffffffffffffffff90921682523360208301528101959095529116929081906060820190565b90612544614eb5565b6040519261255184613d0c565b835281830152611d4e604051928284938452830190613e69565b612576828092613d60565b6105db57806124bb565b823b15610ffd57918791858094604051968795869485937fa8027c0f0000000000000000000000000000000000000000000000000000000085526004850160809052806125cc91615372565b6084860160a090526101248601906125e392613fb6565b916125ed90613c12565b67ffffffffffffffff1660a485015260440161260890613bc3565b73ffffffffffffffffffffffffffffffffffffffff1660c48401528b60e48401526126328b613bc3565b73ffffffffffffffffffffffffffffffffffffffff1661010484015283602484015282810360031901604484015261266991613c55565b8a606483015203925af180156105de57908291612688575b8080612423565b8161269291613d60565b6105db578038612681565b6024837f728fe07b00000000000000000000000000000000000000000000000000000000815233600452fd5b508781813d831161270a575b6126df8183613d60565b8101031261070f5761270573ffffffffffffffffffffffffffffffffffffffff91613f95565b6123c9565b503d6126d5565b6040513d86823e3d90fd5b7fa9902c7e000000000000000000000000000000000000000000000000000000008452600452602483fd5b6004837f53ad11d8000000000000000000000000000000000000000000000000000000008152fd5b6127869150883d8a11610847576108398183613d60565b3861233a565b5073ffffffffffffffffffffffffffffffffffffffff61086f602493614571565b50346105db57806003193601126105db57602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b50346105db5760206003193601126105db57602061281d67ffffffffffffffff612809613bfb565b166000526007602052604060002054151590565b6040519015158152f35b50346105db57806003193601126105db57805473ffffffffffffffffffffffffffffffffffffffff811633036128c6577fffffffffffffffffffffffff000000000000000000000000000000000000000060015491338284161760015516825573ffffffffffffffffffffffffffffffffffffffff3391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b6004827f02b543c6000000000000000000000000000000000000000000000000000000008152fd5b50346105db57806003193601126105db57600254600a54600c546040805173ffffffffffffffffffffffffffffffffffffffff94851681529284166020840152921691810191909152606090f35b50346105db5761294b36613e24565b61295793929193614c00565b67ffffffffffffffff8216612979816000526007602052604060002054151590565b156129985750612995929361298f913691613da1565b90614c4b565b80f35b7f1e670e4b000000000000000000000000000000000000000000000000000000008452600452602483fd5b50346105db5760406003193601126105db576129dd613bfb565b906024359067ffffffffffffffff82116105db57602061281d84612a043660048701613e06565b906145a7565b50346105db5760206003193601126105db576004359067ffffffffffffffff82116105db57816004019061010060031984360301126105db5780604051612a5081613cc1565b5280604051612a5e81613cc1565b52606483013560c4840193612a8e612a88612a83612a7c8888614520565b3691613da1565b6148b4565b8361496f565b936084820195612a9d87614571565b73ffffffffffffffffffffffffffffffffffffffff807f0000000000000000000000000000000000000000000000000000000000000000169116036130b257602483019377ffffffffffffffff00000000000000000000000000000000612b0386614592565b60801b16604051907f2cbc26bb000000000000000000000000000000000000000000000000000000008252600482015260208160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa908115611a8f578791613093575b5061306b5767ffffffffffffffff612b9786614592565b16612baf816000526007602052604060002054151590565b1561304057602073ffffffffffffffffffffffffffffffffffffffff60025416916044604051809481937f83826b2b00000000000000000000000000000000000000000000000000000000835260048301523360248301525afa908115611a8f578791613021575b5015612ff557612c2685614592565b92612c3c60a4860194612a04612a7c8785614520565b15612fae57612c5d88612c4e8b614571565b612c5789614592565b90615289565b73ffffffffffffffffffffffffffffffffffffffff600354169283612de0575b505050505060440191612c8f83614571565b612c9883614592565b5073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690813b15610ae4576040517f40c10f1900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff919091166004820152602481018690529082908290604490829084905af180156105de57612dcb575b5050608067ffffffffffffffff60209573ffffffffffffffffffffffffffffffffffffffff612d97612d916105467ffc5e3a5bddc11d92c2dc20fae6f7d5eb989f056be35239f7de7e86150609abc097614592565b96614571565b816040519716875233898801521660408601528560608601521692a260405190612dc082613cc1565b815260405190518152f35b612dd6828092613d60565b6105db5780612d3c565b833b156107b557878795938195938c93604051988997889687957f6371157400000000000000000000000000000000000000000000000000000000875260048701606090528d612e308780615372565b60648a0161010090526101648a0190612e4892613fb6565b94612e5290613c12565b67ffffffffffffffff166084890152604401612e6d90613bc3565b73ffffffffffffffffffffffffffffffffffffffff1660a488015260c4870152612e9690613bc3565b73ffffffffffffffffffffffffffffffffffffffff1660e4860152612ebb9084615372565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c86840301610104870152612ef09291613fb6565b90612efb9083615372565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c85840301610124860152612f309291613fb6565b9060e48a01612f3e91615372565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c84840301610144850152612f739291613fb6565b8b602483015282604483015203925af1801561271157908491612f99575b808080612c7d565b81612fa391613d60565b610ae4578238612f91565b83612fb891614520565b61208c6040519283927f24eb47e5000000000000000000000000000000000000000000000000000000008452602060048501526024840191613fb6565b6024867f728fe07b00000000000000000000000000000000000000000000000000000000815233600452fd5b61303a915060203d602011610847576108398183613d60565b38612c17565b7fa9902c7e000000000000000000000000000000000000000000000000000000008752600452602486fd5b6004867f53ad11d8000000000000000000000000000000000000000000000000000000008152fd5b6130ac915060203d602011610847576108398183613d60565b38612b80565b60248573ffffffffffffffffffffffffffffffffffffffff61086f8a614571565b50346105db57806003193601126105db57602073ffffffffffffffffffffffffffffffffffffffff60035416604051908152f35b50346105db5760406003193601126105db576004359067ffffffffffffffff82116105db57816004019061010060031984360301126105db57613148613afc565b918160405161315681613cc1565b5260648401359360c481019361317b613175612a83612a7c8887614520565b8761496f565b94608483019661318a88614571565b73ffffffffffffffffffffffffffffffffffffffff807f0000000000000000000000000000000000000000000000000000000000000000169116036135da57602484019477ffffffffffffffff000000000000000000000000000000006131f087614592565b60801b16604051907f2cbc26bb000000000000000000000000000000000000000000000000000000008252600482015260208160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa9081156107c15788916135bb575b506107f75767ffffffffffffffff61328487614592565b1661329c816000526007602052604060002054151590565b156107cc57602073ffffffffffffffffffffffffffffffffffffffff60025416916044604051809481937f83826b2b00000000000000000000000000000000000000000000000000000000835260048301523360248301525afa9081156107c157889161359c575b50156107445761331386614592565b9361332960a4870195612a04612a7c8886614520565b15613592577fffffffff000000000000000000000000000000000000000000000000000000001690811561357757613373896133648c614571565b61336d8a614592565b90615302565b73ffffffffffffffffffffffffffffffffffffffff6003541693846133a6575b50505050505060440191612c8f83614571565b843b1561357357868995938c959387938b6040519a8b998a9889977f6371157400000000000000000000000000000000000000000000000000000000895260048901606090526133f68780615372565b60648b0161010090526101648b019061340e92613fb6565b9461341890613c12565b67ffffffffffffffff1660848a015260440161343390613bc3565b73ffffffffffffffffffffffffffffffffffffffff1660a489015260c488015261345c90613bc3565b73ffffffffffffffffffffffffffffffffffffffff1660e48701526134819084615372565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c878403016101048801526134b69291613fb6565b906134c19083615372565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c868403016101248701526134f69291613fb6565b9060e48b0161350491615372565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c858403016101448601526135399291613fb6565b908c6024840152604483015203925af180156127115761355e575b8080808080613393565b9261356c8160449395613d60565b9290613554565b8880fd5b61358d896135848c614571565b612c578a614592565b613373565b612fb88583614520565b6135b5915060203d602011610847576108398183613d60565b38613304565b6135d4915060203d602011610847576108398183613d60565b3861326d565b60248673ffffffffffffffffffffffffffffffffffffffff61086f8b614571565b50346105db57806003193601126105db57602060405160ff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50346105db5760406003193601126105db57613653613bfb565b6024359182151583036105db57610140613716613670858561449d565b6136c660409392935180946fffffffffffffffffffffffffffffffff6080809282815116855263ffffffff6020820151166020860152604081015115156040860152826060820151166060860152015116910152565b60a08301906fffffffffffffffffffffffffffffffff6080809282815116855263ffffffff6020820151166020860152604081015115156040860152826060820151166060860152015116910152565bf35b50346105db5760206003193601126105db57602090613735613b5a565b905073ffffffffffffffffffffffffffffffffffffffff807f0000000000000000000000000000000000000000000000000000000000000000169116146040519015158152f35b50346105db57806003193601126105db57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50346105db5760c06003193601126105db576137e7613b5a565b506137f0613be4565b6137f8613b7d565b50608435917fffffffff00000000000000000000000000000000000000000000000000000000831683036105db5760a4359067ffffffffffffffff82116105db5760a063ffffffff8061ffff61385d88886138563660048b01613c27565b50506142ed565b95926040979194975197885216602087015216604085015216606083015215156080820152f35b50346105db57806003193601126105db5750611d4e6040516138a7604082613d60565b601b81527f4275726e46726f6d4d696e74546f6b656e506f6f6c20322e302e3000000000006020820152604051918291602083526020830190613c55565b50346105db5760c06003193601126105db576138ff613b5a565b613907613be4565b906064357fffffffff000000000000000000000000000000000000000000000000000000008116810361070f5760843567ffffffffffffffff8111610ffd57613954903690600401613c27565b9160a435936002851015610ff95761396f9560443591613ff5565b90604051918291602083016020845282518091526020604085019301915b81811061399b575050500390f35b825173ffffffffffffffffffffffffffffffffffffffff1684528594506020938401939092019160010161398d565b9050346105e95760206003193601126105e9576020907fffffffff00000000000000000000000000000000000000000000000000000000613a09613ac8565b167faff2afbf000000000000000000000000000000000000000000000000000000008114908115613a9e575b8115613a74575b8115613a4a575b5015158152f35b7f01ffc9a70000000000000000000000000000000000000000000000000000000091501483613a43565b7f0e64dd290000000000000000000000000000000000000000000000000000000081149150613a3c565b7f940a15420000000000000000000000000000000000000000000000000000000081149150613a35565b600435907fffffffff0000000000000000000000000000000000000000000000000000000082168203613af757565b600080fd5b602435907fffffffff0000000000000000000000000000000000000000000000000000000082168203613af757565b604435907fffffffff0000000000000000000000000000000000000000000000000000000082168203613af757565b6004359073ffffffffffffffffffffffffffffffffffffffff82168203613af757565b6064359073ffffffffffffffffffffffffffffffffffffffff82168203613af757565b6024359073ffffffffffffffffffffffffffffffffffffffff82168203613af757565b359073ffffffffffffffffffffffffffffffffffffffff82168203613af757565b6024359067ffffffffffffffff82168203613af757565b6004359067ffffffffffffffff82168203613af757565b359067ffffffffffffffff82168203613af757565b9181601f84011215613af75782359167ffffffffffffffff8311613af75760208381860195010111613af757565b919082519283825260005b848110613c9f5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006020809697860101520116010190565b80602080928401015182828601015201613c60565b35908115158203613af757565b6020810190811067ffffffffffffffff821117613cdd57604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040810190811067ffffffffffffffff821117613cdd57604052565b60a0810190811067ffffffffffffffff821117613cdd57604052565b60e0810190811067ffffffffffffffff821117613cdd57604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117613cdd57604052565b92919267ffffffffffffffff8211613cdd5760405191613de9601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200184613d60565b829481845281830111613af7578281602093846000960137010152565b9080601f83011215613af757816020613e2193359101613da1565b90565b906040600319830112613af75760043567ffffffffffffffff81168103613af757916024359067ffffffffffffffff8211613af757613e6591600401613c27565b9091565b613e21916020613e828351604084526040840190613c55565b920151906020818403910152613c55565b9181601f84011215613af75782359167ffffffffffffffff8311613af7576020808501948460051b010111613af757565b9181601f84011215613af75782359167ffffffffffffffff8311613af7576020808501948460081b010111613af757565b67ffffffffffffffff8111613cdd5760051b60200190565b81810292918115918404141715613f2057565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8115613f59570490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b91908203918211613f2057565b519073ffffffffffffffffffffffffffffffffffffffff82168203613af757565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b92959390919473ffffffffffffffffffffffffffffffffffffffff600354169586156142cb578097600287101561429c5773ffffffffffffffffffffffffffffffffffffffff98614156957fffffffff0000000000000000000000000000000000000000000000000000000093896142725767ffffffffffffffff8216600052600b6020526040600020906040519161408d83613d44565b549163ffffffff8316815263ffffffff8360201c16602082015263ffffffff8360401c16604082015263ffffffff8360601c16606082015260c061ffff8460801c169182608082015260ff60a082019561ffff8160901c16875260a01c161515918291015261421e575b50505067ffffffffffffffff905b6040519b8c997f06b859ef000000000000000000000000000000000000000000000000000000008b521660048a0152166024880152604487015216606485015260c0608485015260c4840191613fb6565b928180600095869560a483015203915afa91821561421157819261417957505090565b9091503d8083833e61418b8183613d60565b810190602081830312610ae45780519067ffffffffffffffff821161070f570181601f82011215610ae4578051906141c282613ef5565b936141d06040519586613d60565b82855260208086019360051b8301019384116105db5750602001905b8282106141f95750505090565b6020809161420684613f95565b8152019101906141ec565b50604051903d90823e3d90fd5b92935067ffffffffffffffff928587161561425a575061271061424961ffff61425094511683613f0d565b0490613f88565b915b9038806140f7565b61426c92506142496127109183613f0d565b91614252565b67ffffffffffffffff91925061429690614290612a8336898b613da1565b9061496f565b91614105565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b50505050505050506040516142e1602082613d60565b60008152600036813790565b67ffffffffffffffff9092919261432b7fffffffff0000000000000000000000000000000000000000000000000000000060025460401b1685614a7c565b16600052600b60205260406000206040519061434682613d44565b549163ffffffff83169384835263ffffffff8460201c169384602085015263ffffffff8160401c169182604086015263ffffffff8260601c169081606087015261ffff8360801c169586608082015260ff61ffff8560901c16948560a084015260a01c16159060c082159101526143f3577fffffffff00000000000000000000000000000000000000000000000000000000166143e857505093929190600190565b959493509160019150565b5050505092505050600090600090600090600090600090565b6040519061441982613d28565b60006080838281528260208201528260408201528260608201520152565b9060405161444481613d28565b60806001829460ff81546fffffffffffffffffffffffffffffffff8116865263ffffffff81861c16602087015260a01c161515604085015201546fffffffffffffffffffffffffffffffff81166060840152811c910152565b67ffffffffffffffff916144af61440c565b506144b861440c565b506144ec57166000526008602052604060002090613e216144e060026144e56144e086614437565b614b63565b9401614437565b16908160005260046020526145076144e06040600020614437565b916000526005602052613e216144e06040600020614437565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215613af7570180359067ffffffffffffffff8211613af757602001918136038313613af757565b3573ffffffffffffffffffffffffffffffffffffffff81168103613af75790565b3567ffffffffffffffff81168103613af75790565b9067ffffffffffffffff613e2192166000526008602052600560406000200190602081519101209060019160005201602052604060002054151590565b604051906145f182613d0c565b60606020838281520152565b80518210156146115760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90600182811c92168015614689575b602083101461465a57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f169161464f565b90604051918260008254926146a784614640565b808452936001811690811561471557506001146146ce575b506146cc92500383613d60565b565b90506000929192526020600020906000915b8183106146f95750509060206146cc92820101386146bf565b60209193508060019154838589010152019101909184926146e0565b602093506146cc9592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091501682840152151560051b820101386146bf565b67ffffffffffffffff166000526008602052613e216004604060002001614693565b91908110156146115760081b0190565b358015158103613af75790565b3561ffff81168103613af75790565b3563ffffffff81168103613af75790565b359063ffffffff82168203613af757565b359061ffff82168203613af757565b91908110156146115760051b0190565b35906fffffffffffffffffffffffffffffffff82168203613af757565b9190826060910312613af7576040516060810181811067ffffffffffffffff821117613cdd57604052604061485481839561483b81613cb4565b8552614849602082016147e4565b6020860152016147e4565b910152565b6fffffffffffffffffffffffffffffffff6148976040809361487a81613cb4565b151586528361488b602083016147e4565b166020870152016147e4565b16910152565b8181106148a8575050565b6000815560010161489d565b80518015614924576020036148e6578051602082810191830183900312613af757519060ff82116148e6575060ff1690565b61208c906040519182917f953576f7000000000000000000000000000000000000000000000000000000008352602060048401526024830190613c55565b50507f000000000000000000000000000000000000000000000000000000000000000090565b9060ff8091169116039060ff8211613f2057565b60ff16604d8111613f2057600a0a90565b907f00000000000000000000000000000000000000000000000000000000000000009060ff82169060ff811692828414614a7557828411614a4b57906149b49161494a565b91604d60ff8416118015614a12575b6149dc575050906149d6613e219261495e565b90613f0d565b9091507fa9cb113d0000000000000000000000000000000000000000000000000000000060005260045260245260445260646000fd5b50614a1c8361495e565b8015613f59577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0484116149c3565b614a549161494a565b91604d60ff8416116149dc57505090614a6f613e219261495e565b90613f4f565b5050505090565b7fffffffff000000000000000000000000000000000000000000000000000000008116908115614b5e57614aaf816151ae565b7dffff00000000000000000000000000000000000000000000000000000000601082811c9085901c1616614b5e5761ffff8360e01c168015918215614b4d575b5050614af9575050565b7fffffffff0000000000000000000000000000000000000000000000000000000092507fdf63778f000000000000000000000000000000000000000000000000000000006000526004521660245260446000fd5b60e01c61ffff161090503880614aef565b505050565b614b6b61440c565b506fffffffffffffffffffffffffffffffff6060820151166fffffffffffffffffffffffffffffffff8083511691614bc86020850193614bc2614bb563ffffffff87511642613f88565b8560808901511690613f0d565b906151a1565b80821015614be157505b16825263ffffffff4216905290565b9050614bd2565b90816020910312613af757518015158103613af75790565b73ffffffffffffffffffffffffffffffffffffffff600154163303614c2157565b7f2b5c74de0000000000000000000000000000000000000000000000000000000060005260046000fd5b90805115614e8b5767ffffffffffffffff81516020830120921691826000526008602052614c80816005604060002001615805565b15614e475760005260096020526040600020815167ffffffffffffffff8111613cdd57614cad8254614640565b601f8111614e15575b506020601f8211600114614d4f5791614d29827f7d628c9a1796743d365ab521a8b2a4686e419b3269919dc9145ea2ce853b54ea9593614d3f95600091614d44575b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b9055604051918291602083526020830190613c55565b0390a2565b905084015138614cf8565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082169083600052806000209160005b818110614dfd575092614d3f9492600192827f7d628c9a1796743d365ab521a8b2a4686e419b3269919dc9145ea2ce853b54ea989610614dc6575b5050811b019055611d3a565b8501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690553880614dba565b9192602060018192868a015181550194019201614d7f565b614e4190836000526020600020601f840160051c81019160208510610f3457601f0160051c019061489d565b38614cb6565b509061208c6040519283927f393b8ad20000000000000000000000000000000000000000000000000000000084526004840152604060248401526044830190613c55565b7f14c880ca0000000000000000000000000000000000000000000000000000000060005260046000fd5b60405160ff7f000000000000000000000000000000000000000000000000000000000000000016602082015260208152613e21604082613d60565b815191929115615072576fffffffffffffffffffffffffffffffff6040840151166fffffffffffffffffffffffffffffffff6020850151161061500f576146cc91925b805182547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690151560a01b74ff0000000000000000000000000000000000000000161782556020810151825460409290920151608090811b7fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff9290921691821760018501557fffffffffffffffffffffffff0000000000000000000000000000000000000000909216174290911b73ffffffff0000000000000000000000000000000016179055565b606483615070604051917f8020d12400000000000000000000000000000000000000000000000000000000835260048301906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565bfd5b6fffffffffffffffffffffffffffffffff60408401511615801590615101575b6150a0576146cc9192614f33565b606483615070604051917fd68af9cc00000000000000000000000000000000000000000000000000000000835260048301906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565b506fffffffffffffffffffffffffffffffff6020840151161515615092565b906127109167ffffffffffffffff61513a60208301614592565b166000908152600b60205260409020917fffffffff00000000000000000000000000000000000000000000000000000000161561518b57606061ffff615187935460901c16910135613f0d565b0490565b606061ffff615187935460801c16910135613f0d565b91908201809211613f2057565b7fffffffff000000000000000000000000000000000000000000000000000000008116908115615285577dffff0000000000000000000000000000000000000000000000000000000081161561527c5760ff60015b169060f01c80615246575b506001036152195750565b7fc512f96c0000000000000000000000000000000000000000000000000000000060005260045260246000fd5b60005b60108110615257575061520e565b6001811b821661526a575b600101615249565b9160018101809111613f205791615262565b60ff6000615203565b5050565b9167ffffffffffffffff7f50f6fbee3ceedce6b7fd7eaef18244487867e6718aec7208187efb6b7908c14c9216928360005260086020526152d28183600260406000200161585a565b6040805173ffffffffffffffffffffffffffffffffffffffff909216825260208201929092529081908101614d3f565b91909167ffffffffffffffff83169283600052600560205260ff60406000205460a01c16156153675750907fc6735cd4fa2bbe7b203b1682936e6ee61bc1702464bbbd12abb6630229d9a5f9918360005260056020526152d28183604060002061585a565b906146cc9350615289565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe182360301811215613af757016020813591019167ffffffffffffffff8211613af7578136038313613af757565b9167ffffffffffffffff7fff0133389f9bb82d5b9385826160eaf2328039f6fa950eeb8cf0836da81789449216928360005260086020526152d28183604060002061585a565b91909167ffffffffffffffff83169283600052600460205260ff60406000205460a01c161561546d5750907f28d6c52e2b0b7587b0d195539fbe6af984b28791aca4d2cc0844244e38bce29e918360005260046020526152d28183604060002061585a565b906146cc93506153c2565b906040519182815491828252602082019060005260206000209260005b8181106154aa5750506146cc92500383613d60565b8454835260019485019487945060209093019201615495565b80548210156146115760005260206000200190600090565b600081815260076020526040902054801561566a577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818111613f2057600654907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201918211613f20578181036155fb575b50505060065480156155cc577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff016155898160066154c3565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82549160031b1b19169055600655600052600760205260006040812055600190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b61565261560c61561d9360066154c3565b90549060031b1c92839260066154c3565b81939154907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060031b92831b921b19161790565b90556000526007602052604060002055388080615550565b5050600090565b906001820191816000528260205260406000205480151560001461579c577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818111613f20578254907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201918211613f2057818103615765575b505050805480156155cc577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff019061572682826154c3565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82549160031b1b191690555560005260205260006040812055600190565b61578561577561561d93866154c3565b90549060031b1c928392866154c3565b9055600052836020526040600020553880806156ee565b50505050600090565b806000526007602052604060002054156000146157ff5760065468010000000000000000811015613cdd576157e661561d82600185940160065560066154c3565b9055600654906000526007602052604060002055600190565b50600090565b600082815260018201602052604090205461566a5780549068010000000000000000821015613cdd578261584361561d8460018096018555846154c3565b905580549260005201602052604060002055600190565b9182549060ff8260a01c16158015615b0e575b615b08576fffffffffffffffffffffffffffffffff821691600185019081546158b263ffffffff6fffffffffffffffffffffffffffffffff83169360801c1642613f88565b9081615a6a575b5050848110615a1e57508383106159135750506158e86fffffffffffffffffffffffffffffffff928392613f88565b16167fffffffffffffffffffffffffffffffff00000000000000000000000000000000825416179055565b9190915460801c9283156159b2578161592b91613f88565b927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810190808211613f205761597961597e9273ffffffffffffffffffffffffffffffffffffffff966151a1565b613f4f565b7fd0c8d23a000000000000000000000000000000000000000000000000000000006000526004526024521660445260646000fd5b73ffffffffffffffffffffffffffffffffffffffff83837fd0c8d23a000000000000000000000000000000000000000000000000000000006000527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6004526024521660445260646000fd5b828573ffffffffffffffffffffffffffffffffffffffff927f1a76572a000000000000000000000000000000000000000000000000000000006000526004526024521660445260646000fd5b828692939611615ade57615a8592614bc29160801c90613f0d565b80841015615ad95750825b85547fffffffffffffffffffffffff00000000ffffffffffffffffffffffffffffffff164260801b73ffffffff00000000000000000000000000000000161786559238806158b9565b615a90565b7f9725942a0000000000000000000000000000000000000000000000000000000060005260046000fd5b50505050565b50821561586d56fea164736f6c634300081a000a' as const +// generate:end diff --git a/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/burn-mint-token-pool.ts b/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/burn-mint-token-pool.ts new file mode 100644 index 000000000..d8a28816a --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/burn-mint-token-pool.ts @@ -0,0 +1,4 @@ +export default // generate: +// `'${require('fs').readFileSync(require('module').createRequire(`${process.cwd()}/package.json`).resolve('@chainlink/contracts-ccip/bytecode/v2_0_0/burn_mint_token_pool.bin'), 'utf8').trim()}' as const` +'0x60e080604052346101f65760a081615db2803803809161001f8285610247565b8339810103126101f65780516001600160a01b038116908190036101f65761004960208301610280565b6100556040840161028e565b9161006e60806100676060870161028e565b950161028e565b93331561023657600180546001600160a01b0319163317905581158015610225575b8015610214575b610203578160805260c052308103610170575b5060a052600380546001600160a01b039283166001600160a01b03199182161790915560028054939092169216919091179055604051615b0f90816102a3823960805181818161023e01528181610491015281816122660152818161243e01528181612aa101528181612c9c0152818161318e0152818161373b0152613795015260a051818181613601015281816149140152818161495e0152614ea8015260c0518181816102d9015281816113eb0152818161230001528181612b3c01526132290152f35b60206004916040519283809263313ce56760e01b82525afa600091816101c2575b50156100aa5760ff1660ff82168181036101ab57506100aa565b6332ad3e0760e11b60005260045260245260446000fd5b9091506020813d6020116101fb575b816101de60209383610247565b810103126101f6576101ef90610280565b9038610191565b600080fd5b3d91506101d1565b630a64406560e11b60005260046000fd5b506001600160a01b03811615610097565b506001600160a01b03851615610090565b639b15e16f60e01b60005260046000fd5b601f909101601f19168101906001600160401b0382119082101761026a57604052565b634e487b7160e01b600052604160045260246000fd5b519060ff821682036101f657565b51906001600160a01b03821682036101f65756fe608080604052600436101561001357600080fd5b600090813560e01c90816301ffc9a7146139b65750806306b859ef146138d1578063181f5a77146138705780631826b1e7146137b957806321df0da714613768578063240028e8146137045780632422ac451461362557806324f65ee7146135e75780632cab0fb6146130f357806337a3210d146130bf57806339077537146129f65780634c5ef0ed146129af57806362ddd3c4146129285780637437ff9f146128da57806379ba5097146128135780638926f54f146127cd5780638da5cb5b146127995780639a4575b9146121ed578063a42a7b8b14612086578063acfecf9114611f8e578063ae39a25714611e03578063b6cfa3b714611d48578063b794658014611d10578063bfeffd3f14611c64578063c4bffe2b14611b39578063c7230a6014611893578063dc04fa1f1461140f578063dc0bd971146113be578063dcbd41bc146111ba578063e8a1da1714610ade578063ea6396db146109a0578063ec6ae7a71461095d578063f2fde38b1461088e5763fbc801a71461019757600080fd5b346105d15760606003193601126105d1576004359067ffffffffffffffff82116105d1578160040160a060031984360301126105df576101d5613ae8565b9060443567ffffffffffffffff811161070557906101fa610217923690600401613c13565b92906102046145d0565b5061020f858461510c565b933691613d8d565b9260848601936102268561455d565b73ffffffffffffffffffffffffffffffffffffffff807f00000000000000000000000000000000000000000000000000000000000000001691160361084457602487019677ffffffffffffffff0000000000000000000000000000000061028c8961457e565b60801b16604051907f2cbc26bb000000000000000000000000000000000000000000000000000000008252600482015260208160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa9081156107b7578891610815575b506107ed5767ffffffffffffffff6103208961457e565b16610338816000526007602052604060002054151590565b156107c257602073ffffffffffffffffffffffffffffffffffffffff60025416916024604051809481937fa8d87a3b00000000000000000000000000000000000000000000000000000000835260048301525afa80156107b7578890610766575b73ffffffffffffffffffffffffffffffffffffffff915016330361073a576064810135936103c78686613f74565b7fffffffff00000000000000000000000000000000000000000000000000000000851694851561071857610423907fffffffff0000000000000000000000000000000000000000000000000000000060025460401b1690614a68565b61043f816104308a61455d565b6104398d61457e565b906153f4565b73ffffffffffffffffffffffffffffffffffffffff6003541693846105e3575b5050505050509061046f91613f74565b916104798461457e565b5073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016803b156105df578180916024604051809481937f42966c680000000000000000000000000000000000000000000000000000000083528960048401525af180156105d4576105bc575b6105b28461058161057c88877ff33bc26b4413b0e7f19f1ea739fdf99098c0061f1f87d954b11f5293fad9ae1067ffffffffffffffff61054261053c8561457e565b9361455d565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252336020830152810188905292169180606081015b0390a261457e565b614741565b9061058a614ea1565b6040519261059784613cf8565b83526020830152604051928392604084526040840190613e55565b9060208301520390f35b6105c7828092613d4c565b6105d157806104fa565b80fd5b6040513d84823e3d90fd5b5080fd5b843b15610714578994928b9694928692604051988997889687957fa8027c0f0000000000000000000000000000000000000000000000000000000087526004870160809052806106329161535e565b6084880160a0905261012488019061064992613fa2565b9261065390613bfe565b67ffffffffffffffff1660a487015260440161066e90613baf565b73ffffffffffffffffffffffffffffffffffffffff1660c48601528d8c60e487015261069990613baf565b73ffffffffffffffffffffffffffffffffffffffff1661010486015260248501528381036003190160448501526106cf91613c41565b90606483015203925af18015610709579085916106f0575b8080808061045f565b816106fa91613d4c565b6107055783386106e7565b8380fd5b6040513d87823e3d90fd5b8980fd5b50610735816107268a61455d565b61072f8d61457e565b906153ae565b61043f565b6024877f728fe07b00000000000000000000000000000000000000000000000000000000815233600452fd5b506020813d6020116107af575b8161078060209383613d4c565b810103126107ab576107a673ffffffffffffffffffffffffffffffffffffffff91613f81565b610399565b8780fd5b3d9150610773565b6040513d8a823e3d90fd5b7fa9902c7e000000000000000000000000000000000000000000000000000000008852600452602487fd5b6004877f53ad11d8000000000000000000000000000000000000000000000000000000008152fd5b610837915060203d60201161083d575b61082f8183613d4c565b810190614bd4565b38610309565b503d610825565b60248673ffffffffffffffffffffffffffffffffffffffff6108658861455d565b7f961c9a4f00000000000000000000000000000000000000000000000000000000835216600452fd5b50346105d15760206003193601126105d15773ffffffffffffffffffffffffffffffffffffffff6108bd613b46565b6108c5614bec565b1633811461093557807fffffffffffffffffffffffff000000000000000000000000000000000000000083541617825573ffffffffffffffffffffffffffffffffffffffff600154167fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12788380a380f35b6004827fdad89dca000000000000000000000000000000000000000000000000000000008152fd5b50346105d157806003193601126105d15760207fffffffff0000000000000000000000000000000000000000000000000000000060025460401b16604051908152f35b50346105d15760806003193601126105d1576109ba613b46565b506109c3613bd0565b6109cb613b17565b5060643567ffffffffffffffff8111610ada579167ffffffffffffffff6040926109fb60e0953690600401613c13565b50508260c08551610a0b81613d30565b82815282602082015282878201528260608201528260808201528260a08201520152168152600b6020522060405190610a4382613d30565b5461ffff818163ffffffff82169485815263ffffffff60208201818560201c1681528160408401818760401c168152816060860193818960601c16855260ff60c060808901988a8c60801c168a528a60a082019c60901c168c52019b60a01c1615158b526040519b8c52511660208b0152511660408901525116606087015251166080850152511660a083015251151560c0820152f35b8280fd5b50346105d15760406003193601126105d15760043567ffffffffffffffff81116105df57610b10903690600401613e7f565b9060243567ffffffffffffffff81116107055790610b3384923690600401613e7f565b939091610b3e614bec565b83905b828210610ffb5750505081927ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee182360301935b81811015610ff7578060051b83013585811215610ff357830161012081360312610ff35760405194610ba586613d14565b610bae82613bfe565b8652602082013567ffffffffffffffff81116105df5782019436601f870112156105df57853595610bde87613ee1565b96610bec6040519889613d4c565b80885260208089019160051b83010190368211610ff35760208301905b828210610fc0575050505060208701958652604083013567ffffffffffffffff8111610ada57610c3c9036908501613df2565b9160408801928352610c66610c5436606087016147ed565b9460608a0195865260c03691016147ed565b956080890196875283515115610f9857610c8a67ffffffffffffffff8a5116615791565b15610f615767ffffffffffffffff8951168252600860205260408220610cb1865182614edc565b610cbf885160028301614edc565b6004855191019080519067ffffffffffffffff8211610f3457610ce2835461462c565b601f8111610ef9575b50602090601f8311600114610e5a57610d399291869183610e4f575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b90555b815b88518051821015610d735790610d6d600192610d668367ffffffffffffffff8f5116926145e9565b5190614c37565b01610d3e565b5050977f8d340f17e19058004c20453540862a9c62778504476f6756755cb33bcd6c38c2939199975095610e4167ffffffffffffffff6001979694985116925193519151610e0d610dd860405196879687526101006020880152610100870190613c41565b9360408601906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565b60a08401906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565b0390a1019392909193610b74565b015190508e80610d07565b83865281862091907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08416875b818110610ee15750908460019594939210610eaa575b505050811b019055610d3c565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558d8080610e9d565b92936020600181928786015181550195019301610e87565b610f249084875260208720601f850160051c81019160208610610f2a575b601f0160051c0190614889565b8d610ceb565b9091508190610f17565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b60248267ffffffffffffffff8b51167f1d5ad3c5000000000000000000000000000000000000000000000000000000008252600452fd5b6004827f14c880ca000000000000000000000000000000000000000000000000000000008152fd5b813567ffffffffffffffff8111610fef57602091610fe48392833691890101613df2565b815201910190610c09565b8680fd5b8480fd5b8380f35b9267ffffffffffffffff61101d6110188486889a9699979a6147c0565b61457e565b1691611028836154c7565b1561118e57828452600860205261104460056040862001615464565b94845b865181101561107d5760019085875260086020526110766005604089200161106f838b6145e9565b519061565d565b5001611047565b50939692909450949094808752600860205260056040882088815588600182015588600282015588600382015588600482016110b9815461462c565b8061114d575b505050018054908881558161112f575b5050907f5204aec90a3c794d8e90fded8b46ae9c7c552803e7e832e0c1d358396d8599166020836001948a52600482528985604082208281550155808a52600582528985604082208281550155604051908152a101909194939294610b41565b885260208820908101905b818110156110cf5788815560010161113a565b601f81116001146111635750555b888a806110bf565b8183526020832061117e91601f01861c810190600101614889565b808252816020812091555561115b565b602484847f1e670e4b000000000000000000000000000000000000000000000000000000008252600452fd5b50346105d15760206003193601126105d15760043567ffffffffffffffff81116105df576111ec903690600401613eb0565b73ffffffffffffffffffffffffffffffffffffffff600a54163314158061139c575b61137057825b81811061121f578380f35b61122a818385614763565b67ffffffffffffffff61123c8261457e565b1690611255826000526007602052604060002054151590565b1561134457907f41f7c8f7cfdad9350aa495e6c54cbbf750a07ab38a9098aed1256e30dd1682bb60e0836113046112de602060019897018b61129682614773565b1561130b5787905260046020526112bd60408d206112b736604088016147ed565b90614edc565b868c5260056020526112d960408d206112b73660a088016147ed565b614773565b9160405192151583526112f76020840160408301614845565b60a0608084019101614845565ba201611214565b60026040828a6112d99452600860205261132d8282206112b736858c016147ed565b8a8152600860205220016112b73660a088016147ed565b602486837f1e670e4b000000000000000000000000000000000000000000000000000000008252600452fd5b6024837f8e4a23d600000000000000000000000000000000000000000000000000000000815233600452fd5b5073ffffffffffffffffffffffffffffffffffffffff6001541633141561120e565b50346105d157806003193601126105d157602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50346105d15760406003193601126105d15760043567ffffffffffffffff81116105df57611441903690600401613eb0565b60243567ffffffffffffffff811161070557611461903690600401613e7f565b91909261146c614bec565b845b8281106114d857505050825b818110611485578380f35b8067ffffffffffffffff61149f61101860019486886147c0565b16808652600b6020528560408120557f5479bbc0288b7eaeaf2ace0943b88016cc648964fcd42919a86fd93b15fdbee88680a20161147a565b67ffffffffffffffff6114ef611018838686614763565b16611507816000526007602052604060002054151590565b1561186857611517828585614763565b602081019060e081019061152a82614773565b1561183c5760a0810161271061ffff61154283614780565b16101561182d5760c082019161271061ffff61155d85614780565b1610156117f55763ffffffff6115728661478f565b16156117c957858c52600b60205260408c2061158d8661478f565b63ffffffff169080549060408401916115a58361478f565b60201b67ffffffff00000000169360608601946115c18661478f565b60401b6bffffffff00000000000000001696608001966115e08861478f565b60601b6fffffffff00000000000000000000000016916115ff8a614780565b60801b71ffff0000000000000000000000000000000016936116208c614780565b60901b73ffff00000000000000000000000000000000000016957fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016177fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff16177fffffffffffffffffffffffffffff0000ffffffffffffffffffffffffffffffff16177fffffffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffff1617171781556116d387614773565b81547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690151560a01b74ff00000000000000000000000000000000000000001617905560405196611724906147a0565b63ffffffff168752611735906147a0565b63ffffffff166020870152611749906147a0565b63ffffffff16604086015261175d906147a0565b63ffffffff166060850152611771906147b1565b61ffff166080840152611783906147b1565b61ffff1660a083015261179590613ca0565b151560c082015260e07ffae1e296719dac5269c3886fb5002bb29bf17ae403060c6eb063a55abaaa104191a260010161146e565b60248c877f12332265000000000000000000000000000000000000000000000000000000008252600452fd5b60248c61ffff61180486614780565b7f95f3517a00000000000000000000000000000000000000000000000000000000835216600452fd5b8a61ffff611804602493614780565b60248a857f12332265000000000000000000000000000000000000000000000000000000008252600452fd5b7f1e670e4b000000000000000000000000000000000000000000000000000000008752600452602486fd5b50346105d15760406003193601126105d15760043567ffffffffffffffff81116105df576118c5903690600401613e7f565b906118ce613b8c565b9173ffffffffffffffffffffffffffffffffffffffff6001541633141580611b17575b611aeb5773ffffffffffffffffffffffffffffffffffffffff8316908115611ac357845b818110611920578580f35b73ffffffffffffffffffffffffffffffffffffffff6119486119438385886147c0565b61455d565b166040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152602081602481855afa9081156107b7578891611a90575b508061199d575b5050600101611915565b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000602080830191825273ffffffffffffffffffffffffffffffffffffffff8a16602484015260448084018590528352918a91906119fe606482613d4c565b519082865af115611a855787513d611a7c5750813b155b611a505790847f508d7d183612c18fc339b42618912b9fa3239f631dd7ec0671f950200a0fa66e602060019594604051908152a39038611993565b602488837f5274afe7000000000000000000000000000000000000000000000000000000008252600452fd5b60011415611a15565b6040513d89823e3d90fd5b905060203d8111611abc575b611aa68183613d4c565b602082600092810103126105d15750513861198c565b503d611a9c565b6004857f8579befe000000000000000000000000000000000000000000000000000000008152fd5b6024847fcb1afbd700000000000000000000000000000000000000000000000000000000815233600452fd5b5073ffffffffffffffffffffffffffffffffffffffff600c54163314156118f1565b50346105d157806003193601126105d157604051906006548083528260208101600684526020842092845b818110611c4b575050611b7992500383613d4c565b8151611b9d611b8782613ee1565b91611b956040519384613d4c565b808352613ee1565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602083019301368437805b8451811015611bfc578067ffffffffffffffff611be9600193886145e9565b5116611bf582866145e9565b5201611bca565b50925090604051928392602084019060208552518091526040840192915b818110611c28575050500390f35b825167ffffffffffffffff16845285945060209384019390920191600101611c1a565b8454835260019485019487945060209093019201611b64565b50346105d15760206003193601126105d15760043573ffffffffffffffffffffffffffffffffffffffff81168091036105df57611c9f614bec565b7fffffffffffffffffffffffff00000000000000000000000000000000000000006003547fbaff46844acf36d6ee996f489a1a288709c4542bd33cd557770afd267d5812096040805173ffffffffffffffffffffffffffffffffffffffff84168152856020820152a1161760035580f35b50346105d15760206003193601126105d157611d44611d3061057c613be7565b604051918291602083526020830190613c41565b0390f35b50346105d15760206003193601126105d1577f307cf716eade81675bea3ccb6917b0f91baa2160056765d9a83d76f819caf06a6020611d85613ab4565b611d8d614bec565b6002547fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff77ffffffff0000000000000000000000000000000000000000808460401c16169116176002557fffffffff0000000000000000000000000000000000000000000000000000000060405191168152a180f35b50346105d15760606003193601126105d157611e1d613b46565b90611e26613b8c565b6044359273ffffffffffffffffffffffffffffffffffffffff841680850361070557611e50614bec565b73ffffffffffffffffffffffffffffffffffffffff82168015611f665794611f60917f3f1036e85d016a93254a0b1415844f79b85424959d90ae5ad51ce8f4533fe70195967fffffffffffffffffffffffff0000000000000000000000000000000000000000600254161760025573ffffffffffffffffffffffffffffffffffffffff85167fffffffffffffffffffffffff0000000000000000000000000000000000000000600a541617600a557fffffffffffffffffffffffff0000000000000000000000000000000000000000600c541617600c556040519384938491604091949373ffffffffffffffffffffffffffffffffffffffff809281606087019816865216602085015216910152565b0390a180f35b6004857f14c880ca000000000000000000000000000000000000000000000000000000008152fd5b50346105d15767ffffffffffffffff611fa636613e10565b929091611fb1614bec565b1691611fca836000526007602052604060002054151590565b1561118e578284526008602052611ff960056040862001611fec368486613d8d565b602081519101209061565d565b1561203e57907f52d00ee4d9bd51b40168f2afc5848837288ce258784ad914278791464b3f4d7691612038604051928392602084526020840191613fa2565b0390a280f35b82612082836040519384937f74f23c7c0000000000000000000000000000000000000000000000000000000085526004850152604060248501526044840191613fa2565b0390fd5b50346105d15760206003193601126105d15767ffffffffffffffff6120a9613be7565b16815260086020526120c060056040832001615464565b80517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06121056120ef83613ee1565b926120fd6040519485613d4c565b808452613ee1565b01835b8181106121dc575050825b82518110156121595780612129600192856145e9565b518552600960205261213d6040862061467f565b61214782856145e9565b5261215281846145e9565b5001612113565b81846040519182916020830160208452825180915260408401602060408360051b870101940192905b82821061219157505050500390f35b919360206121cc827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc060019597998495030186528851613c41565b9601920192018594939192612182565b806060602080938601015201612108565b50346105d15760206003193601126105d15760043567ffffffffffffffff81116105df57806004019060a06003198236030112610ada5761222c6145d0565b5060405160209361223d8583613d4c565b808252608483019161224e8361455d565b73ffffffffffffffffffffffffffffffffffffffff807f00000000000000000000000000000000000000000000000000000000000000001691160361277857602484019477ffffffffffffffff000000000000000000000000000000006122b48761457e565b60801b16604051907f2cbc26bb0000000000000000000000000000000000000000000000000000000082526004820152878160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa9081156126fd57849161275b575b506127335767ffffffffffffffff6123478761457e565b1661235f816000526007602052604060002054151590565b15612708578773ffffffffffffffffffffffffffffffffffffffff60025416916024604051809481937fa8d87a3b00000000000000000000000000000000000000000000000000000000835260048301525afa80156126fd5784906126b5575b73ffffffffffffffffffffffffffffffffffffffff9150163303612689576064850135946123f9866123f08761455d565b61072f8a61457e565b73ffffffffffffffffffffffffffffffffffffffff60035416918261256c575b505050506124268461457e565b5073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016803b156105df578180916024604051809481937f42966c680000000000000000000000000000000000000000000000000000000083528960048401525af180156105d457612557575b8561252761057c87877ff33bc26b4413b0e7f19f1ea739fdf99098c0061f1f87d954b11f5293fad9ae1067ffffffffffffffff896105746124f06124ea8761457e565b9261455d565b6040805173ffffffffffffffffffffffffffffffffffffffff90921682523360208301528101959095529116929081906060820190565b90612530614ea1565b6040519261253d84613cf8565b835281830152611d44604051928284938452830190613e55565b612562828092613d4c565b6105d157806124a7565b823b15610ff357918791858094604051968795869485937fa8027c0f0000000000000000000000000000000000000000000000000000000085526004850160809052806125b89161535e565b6084860160a090526101248601906125cf92613fa2565b916125d990613bfe565b67ffffffffffffffff1660a48501526044016125f490613baf565b73ffffffffffffffffffffffffffffffffffffffff1660c48401528b60e484015261261e8b613baf565b73ffffffffffffffffffffffffffffffffffffffff1661010484015283602484015282810360031901604484015261265591613c41565b8a606483015203925af180156105d457908291612674575b8080612419565b8161267e91613d4c565b6105d157803861266d565b6024837f728fe07b00000000000000000000000000000000000000000000000000000000815233600452fd5b508781813d83116126f6575b6126cb8183613d4c565b81010312610705576126f173ffffffffffffffffffffffffffffffffffffffff91613f81565b6123bf565b503d6126c1565b6040513d86823e3d90fd5b7fa9902c7e000000000000000000000000000000000000000000000000000000008452600452602483fd5b6004837f53ad11d8000000000000000000000000000000000000000000000000000000008152fd5b6127729150883d8a1161083d5761082f8183613d4c565b38612330565b5073ffffffffffffffffffffffffffffffffffffffff61086560249361455d565b50346105d157806003193601126105d157602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b50346105d15760206003193601126105d157602061280967ffffffffffffffff6127f5613be7565b166000526007602052604060002054151590565b6040519015158152f35b50346105d157806003193601126105d157805473ffffffffffffffffffffffffffffffffffffffff811633036128b2577fffffffffffffffffffffffff000000000000000000000000000000000000000060015491338284161760015516825573ffffffffffffffffffffffffffffffffffffffff3391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b6004827f02b543c6000000000000000000000000000000000000000000000000000000008152fd5b50346105d157806003193601126105d157600254600a54600c546040805173ffffffffffffffffffffffffffffffffffffffff94851681529284166020840152921691810191909152606090f35b50346105d15761293736613e10565b61294393929193614bec565b67ffffffffffffffff8216612965816000526007602052604060002054151590565b156129845750612981929361297b913691613d8d565b90614c37565b80f35b7f1e670e4b000000000000000000000000000000000000000000000000000000008452600452602483fd5b50346105d15760406003193601126105d1576129c9613be7565b906024359067ffffffffffffffff82116105d1576020612809846129f03660048701613df2565b90614593565b50346105d15760206003193601126105d1576004359067ffffffffffffffff82116105d157816004019061010060031984360301126105d15780604051612a3c81613cad565b5280604051612a4a81613cad565b52606483013560c4840193612a7a612a74612a6f612a68888861450c565b3691613d8d565b6148a0565b8361495b565b936084820195612a898761455d565b73ffffffffffffffffffffffffffffffffffffffff807f00000000000000000000000000000000000000000000000000000000000000001691160361309e57602483019377ffffffffffffffff00000000000000000000000000000000612aef8661457e565b60801b16604051907f2cbc26bb000000000000000000000000000000000000000000000000000000008252600482015260208160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa908115611a8557879161307f575b506130575767ffffffffffffffff612b838661457e565b16612b9b816000526007602052604060002054151590565b1561302c57602073ffffffffffffffffffffffffffffffffffffffff60025416916044604051809481937f83826b2b00000000000000000000000000000000000000000000000000000000835260048301523360248301525afa908115611a8557879161300d575b5015612fe157612c128561457e565b92612c2860a48601946129f0612a68878561450c565b15612f9a57612c4988612c3a8b61455d565b612c438961457e565b90615275565b73ffffffffffffffffffffffffffffffffffffffff600354169283612dcc575b505050505060440191612c7b8361455d565b612c848361457e565b5073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690813b15610ada576040517f40c10f1900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff919091166004820152602481018690529082908290604490829084905af180156105d457612db7575b5050608067ffffffffffffffff60209573ffffffffffffffffffffffffffffffffffffffff612d83612d7d61053c7ffc5e3a5bddc11d92c2dc20fae6f7d5eb989f056be35239f7de7e86150609abc09761457e565b9661455d565b816040519716875233898801521660408601528560608601521692a260405190612dac82613cad565b815260405190518152f35b612dc2828092613d4c565b6105d15780612d28565b833b156107ab57878795938195938c93604051988997889687957f6371157400000000000000000000000000000000000000000000000000000000875260048701606090528d612e1c878061535e565b60648a0161010090526101648a0190612e3492613fa2565b94612e3e90613bfe565b67ffffffffffffffff166084890152604401612e5990613baf565b73ffffffffffffffffffffffffffffffffffffffff1660a488015260c4870152612e8290613baf565b73ffffffffffffffffffffffffffffffffffffffff1660e4860152612ea7908461535e565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c86840301610104870152612edc9291613fa2565b90612ee7908361535e565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c85840301610124860152612f1c9291613fa2565b9060e48a01612f2a9161535e565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c84840301610144850152612f5f9291613fa2565b8b602483015282604483015203925af180156126fd57908491612f85575b808080612c69565b81612f8f91613d4c565b610ada578238612f7d565b83612fa49161450c565b6120826040519283927f24eb47e5000000000000000000000000000000000000000000000000000000008452602060048501526024840191613fa2565b6024867f728fe07b00000000000000000000000000000000000000000000000000000000815233600452fd5b613026915060203d60201161083d5761082f8183613d4c565b38612c03565b7fa9902c7e000000000000000000000000000000000000000000000000000000008752600452602486fd5b6004867f53ad11d8000000000000000000000000000000000000000000000000000000008152fd5b613098915060203d60201161083d5761082f8183613d4c565b38612b6c565b60248573ffffffffffffffffffffffffffffffffffffffff6108658a61455d565b50346105d157806003193601126105d157602073ffffffffffffffffffffffffffffffffffffffff60035416604051908152f35b50346105d15760406003193601126105d1576004359067ffffffffffffffff82116105d157816004019061010060031984360301126105d157613134613ae8565b918160405161314281613cad565b5260648401359360c4810193613167613161612a6f612a68888761450c565b8761495b565b9460848301966131768861455d565b73ffffffffffffffffffffffffffffffffffffffff807f0000000000000000000000000000000000000000000000000000000000000000169116036135c657602484019477ffffffffffffffff000000000000000000000000000000006131dc8761457e565b60801b16604051907f2cbc26bb000000000000000000000000000000000000000000000000000000008252600482015260208160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa9081156107b75788916135a7575b506107ed5767ffffffffffffffff6132708761457e565b16613288816000526007602052604060002054151590565b156107c257602073ffffffffffffffffffffffffffffffffffffffff60025416916044604051809481937f83826b2b00000000000000000000000000000000000000000000000000000000835260048301523360248301525afa9081156107b7578891613588575b501561073a576132ff8661457e565b9361331560a48701956129f0612a68888661450c565b1561357e577fffffffff00000000000000000000000000000000000000000000000000000000169081156135635761335f896133508c61455d565b6133598a61457e565b906152ee565b73ffffffffffffffffffffffffffffffffffffffff600354169384613392575b50505050505060440191612c7b8361455d565b843b1561355f57868995938c959387938b6040519a8b998a9889977f6371157400000000000000000000000000000000000000000000000000000000895260048901606090526133e2878061535e565b60648b0161010090526101648b01906133fa92613fa2565b9461340490613bfe565b67ffffffffffffffff1660848a015260440161341f90613baf565b73ffffffffffffffffffffffffffffffffffffffff1660a489015260c488015261344890613baf565b73ffffffffffffffffffffffffffffffffffffffff1660e487015261346d908461535e565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c878403016101048801526134a29291613fa2565b906134ad908361535e565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c868403016101248701526134e29291613fa2565b9060e48b016134f09161535e565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c858403016101448601526135259291613fa2565b908c6024840152604483015203925af180156126fd5761354a575b808080808061337f565b926135588160449395613d4c565b9290613540565b8880fd5b613579896135708c61455d565b612c438a61457e565b61335f565b612fa4858361450c565b6135a1915060203d60201161083d5761082f8183613d4c565b386132f0565b6135c0915060203d60201161083d5761082f8183613d4c565b38613259565b60248673ffffffffffffffffffffffffffffffffffffffff6108658b61455d565b50346105d157806003193601126105d157602060405160ff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50346105d15760406003193601126105d15761363f613be7565b6024359182151583036105d15761014061370261365c8585614489565b6136b260409392935180946fffffffffffffffffffffffffffffffff6080809282815116855263ffffffff6020820151166020860152604081015115156040860152826060820151166060860152015116910152565b60a08301906fffffffffffffffffffffffffffffffff6080809282815116855263ffffffff6020820151166020860152604081015115156040860152826060820151166060860152015116910152565bf35b50346105d15760206003193601126105d157602090613721613b46565b905073ffffffffffffffffffffffffffffffffffffffff807f0000000000000000000000000000000000000000000000000000000000000000169116146040519015158152f35b50346105d157806003193601126105d157602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50346105d15760c06003193601126105d1576137d3613b46565b506137dc613bd0565b6137e4613b69565b50608435917fffffffff00000000000000000000000000000000000000000000000000000000831683036105d15760a4359067ffffffffffffffff82116105d15760a063ffffffff8061ffff61384988886138423660048b01613c13565b50506142d9565b95926040979194975197885216602087015216604085015216606083015215156080820152f35b50346105d157806003193601126105d15750611d44604051613893604082613d4c565b601781527f4275726e4d696e74546f6b656e506f6f6c20322e302e300000000000000000006020820152604051918291602083526020830190613c41565b50346105d15760c06003193601126105d1576138eb613b46565b6138f3613bd0565b906064357fffffffff00000000000000000000000000000000000000000000000000000000811681036107055760843567ffffffffffffffff8111610ff357613940903690600401613c13565b9160a435936002851015610fef5761395b9560443591613fe1565b90604051918291602083016020845282518091526020604085019301915b818110613987575050500390f35b825173ffffffffffffffffffffffffffffffffffffffff16845285945060209384019390920191600101613979565b9050346105df5760206003193601126105df576020907fffffffff000000000000000000000000000000000000000000000000000000006139f5613ab4565b167faff2afbf000000000000000000000000000000000000000000000000000000008114908115613a8a575b8115613a60575b8115613a36575b5015158152f35b7f01ffc9a70000000000000000000000000000000000000000000000000000000091501483613a2f565b7f0e64dd290000000000000000000000000000000000000000000000000000000081149150613a28565b7f940a15420000000000000000000000000000000000000000000000000000000081149150613a21565b600435907fffffffff0000000000000000000000000000000000000000000000000000000082168203613ae357565b600080fd5b602435907fffffffff0000000000000000000000000000000000000000000000000000000082168203613ae357565b604435907fffffffff0000000000000000000000000000000000000000000000000000000082168203613ae357565b6004359073ffffffffffffffffffffffffffffffffffffffff82168203613ae357565b6064359073ffffffffffffffffffffffffffffffffffffffff82168203613ae357565b6024359073ffffffffffffffffffffffffffffffffffffffff82168203613ae357565b359073ffffffffffffffffffffffffffffffffffffffff82168203613ae357565b6024359067ffffffffffffffff82168203613ae357565b6004359067ffffffffffffffff82168203613ae357565b359067ffffffffffffffff82168203613ae357565b9181601f84011215613ae35782359167ffffffffffffffff8311613ae35760208381860195010111613ae357565b919082519283825260005b848110613c8b5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006020809697860101520116010190565b80602080928401015182828601015201613c4c565b35908115158203613ae357565b6020810190811067ffffffffffffffff821117613cc957604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040810190811067ffffffffffffffff821117613cc957604052565b60a0810190811067ffffffffffffffff821117613cc957604052565b60e0810190811067ffffffffffffffff821117613cc957604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117613cc957604052565b92919267ffffffffffffffff8211613cc95760405191613dd5601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200184613d4c565b829481845281830111613ae3578281602093846000960137010152565b9080601f83011215613ae357816020613e0d93359101613d8d565b90565b906040600319830112613ae35760043567ffffffffffffffff81168103613ae357916024359067ffffffffffffffff8211613ae357613e5191600401613c13565b9091565b613e0d916020613e6e8351604084526040840190613c41565b920151906020818403910152613c41565b9181601f84011215613ae35782359167ffffffffffffffff8311613ae3576020808501948460051b010111613ae357565b9181601f84011215613ae35782359167ffffffffffffffff8311613ae3576020808501948460081b010111613ae357565b67ffffffffffffffff8111613cc95760051b60200190565b81810292918115918404141715613f0c57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8115613f45570490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b91908203918211613f0c57565b519073ffffffffffffffffffffffffffffffffffffffff82168203613ae357565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b92959390919473ffffffffffffffffffffffffffffffffffffffff600354169586156142b757809760028710156142885773ffffffffffffffffffffffffffffffffffffffff98614142957fffffffff00000000000000000000000000000000000000000000000000000000938961425e5767ffffffffffffffff8216600052600b6020526040600020906040519161407983613d30565b549163ffffffff8316815263ffffffff8360201c16602082015263ffffffff8360401c16604082015263ffffffff8360601c16606082015260c061ffff8460801c169182608082015260ff60a082019561ffff8160901c16875260a01c161515918291015261420a575b50505067ffffffffffffffff905b6040519b8c997f06b859ef000000000000000000000000000000000000000000000000000000008b521660048a0152166024880152604487015216606485015260c0608485015260c4840191613fa2565b928180600095869560a483015203915afa9182156141fd57819261416557505090565b9091503d8083833e6141778183613d4c565b810190602081830312610ada5780519067ffffffffffffffff8211610705570181601f82011215610ada578051906141ae82613ee1565b936141bc6040519586613d4c565b82855260208086019360051b8301019384116105d15750602001905b8282106141e55750505090565b602080916141f284613f81565b8152019101906141d8565b50604051903d90823e3d90fd5b92935067ffffffffffffffff9285871615614246575061271061423561ffff61423c94511683613ef9565b0490613f74565b915b9038806140e3565b61425892506142356127109183613ef9565b9161423e565b67ffffffffffffffff9192506142829061427c612a6f36898b613d8d565b9061495b565b916140f1565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b50505050505050506040516142cd602082613d4c565b60008152600036813790565b67ffffffffffffffff909291926143177fffffffff0000000000000000000000000000000000000000000000000000000060025460401b1685614a68565b16600052600b60205260406000206040519061433282613d30565b549163ffffffff83169384835263ffffffff8460201c169384602085015263ffffffff8160401c169182604086015263ffffffff8260601c169081606087015261ffff8360801c169586608082015260ff61ffff8560901c16948560a084015260a01c16159060c082159101526143df577fffffffff00000000000000000000000000000000000000000000000000000000166143d457505093929190600190565b959493509160019150565b5050505092505050600090600090600090600090600090565b6040519061440582613d14565b60006080838281528260208201528260408201528260608201520152565b9060405161443081613d14565b60806001829460ff81546fffffffffffffffffffffffffffffffff8116865263ffffffff81861c16602087015260a01c161515604085015201546fffffffffffffffffffffffffffffffff81166060840152811c910152565b67ffffffffffffffff9161449b6143f8565b506144a46143f8565b506144d857166000526008602052604060002090613e0d6144cc60026144d16144cc86614423565b614b4f565b9401614423565b16908160005260046020526144f36144cc6040600020614423565b916000526005602052613e0d6144cc6040600020614423565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215613ae3570180359067ffffffffffffffff8211613ae357602001918136038313613ae357565b3573ffffffffffffffffffffffffffffffffffffffff81168103613ae35790565b3567ffffffffffffffff81168103613ae35790565b9067ffffffffffffffff613e0d92166000526008602052600560406000200190602081519101209060019160005201602052604060002054151590565b604051906145dd82613cf8565b60606020838281520152565b80518210156145fd5760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90600182811c92168015614675575b602083101461464657565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f169161463b565b90604051918260008254926146938461462c565b808452936001811690811561470157506001146146ba575b506146b892500383613d4c565b565b90506000929192526020600020906000915b8183106146e55750509060206146b892820101386146ab565b60209193508060019154838589010152019101909184926146cc565b602093506146b89592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091501682840152151560051b820101386146ab565b67ffffffffffffffff166000526008602052613e0d600460406000200161467f565b91908110156145fd5760081b0190565b358015158103613ae35790565b3561ffff81168103613ae35790565b3563ffffffff81168103613ae35790565b359063ffffffff82168203613ae357565b359061ffff82168203613ae357565b91908110156145fd5760051b0190565b35906fffffffffffffffffffffffffffffffff82168203613ae357565b9190826060910312613ae3576040516060810181811067ffffffffffffffff821117613cc957604052604061484081839561482781613ca0565b8552614835602082016147d0565b6020860152016147d0565b910152565b6fffffffffffffffffffffffffffffffff6148836040809361486681613ca0565b1515865283614877602083016147d0565b166020870152016147d0565b16910152565b818110614894575050565b60008155600101614889565b80518015614910576020036148d2578051602082810191830183900312613ae357519060ff82116148d2575060ff1690565b612082906040519182917f953576f7000000000000000000000000000000000000000000000000000000008352602060048401526024830190613c41565b50507f000000000000000000000000000000000000000000000000000000000000000090565b9060ff8091169116039060ff8211613f0c57565b60ff16604d8111613f0c57600a0a90565b907f00000000000000000000000000000000000000000000000000000000000000009060ff82169060ff811692828414614a6157828411614a3757906149a091614936565b91604d60ff84161180156149fe575b6149c8575050906149c2613e0d9261494a565b90613ef9565b9091507fa9cb113d0000000000000000000000000000000000000000000000000000000060005260045260245260445260646000fd5b50614a088361494a565b8015613f45577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0484116149af565b614a4091614936565b91604d60ff8416116149c857505090614a5b613e0d9261494a565b90613f3b565b5050505090565b7fffffffff000000000000000000000000000000000000000000000000000000008116908115614b4a57614a9b8161519a565b7dffff00000000000000000000000000000000000000000000000000000000601082811c9085901c1616614b4a5761ffff8360e01c168015918215614b39575b5050614ae5575050565b7fffffffff0000000000000000000000000000000000000000000000000000000092507fdf63778f000000000000000000000000000000000000000000000000000000006000526004521660245260446000fd5b60e01c61ffff161090503880614adb565b505050565b614b576143f8565b506fffffffffffffffffffffffffffffffff6060820151166fffffffffffffffffffffffffffffffff8083511691614bb46020850193614bae614ba163ffffffff87511642613f74565b8560808901511690613ef9565b9061518d565b80821015614bcd57505b16825263ffffffff4216905290565b9050614bbe565b90816020910312613ae357518015158103613ae35790565b73ffffffffffffffffffffffffffffffffffffffff600154163303614c0d57565b7f2b5c74de0000000000000000000000000000000000000000000000000000000060005260046000fd5b90805115614e775767ffffffffffffffff81516020830120921691826000526008602052614c6c8160056040600020016157f1565b15614e335760005260096020526040600020815167ffffffffffffffff8111613cc957614c99825461462c565b601f8111614e01575b506020601f8211600114614d3b5791614d15827f7d628c9a1796743d365ab521a8b2a4686e419b3269919dc9145ea2ce853b54ea9593614d2b95600091614d30575b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b9055604051918291602083526020830190613c41565b0390a2565b905084015138614ce4565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082169083600052806000209160005b818110614de9575092614d2b9492600192827f7d628c9a1796743d365ab521a8b2a4686e419b3269919dc9145ea2ce853b54ea989610614db2575b5050811b019055611d30565b8501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690553880614da6565b9192602060018192868a015181550194019201614d6b565b614e2d90836000526020600020601f840160051c81019160208510610f2a57601f0160051c0190614889565b38614ca2565b50906120826040519283927f393b8ad20000000000000000000000000000000000000000000000000000000084526004840152604060248401526044830190613c41565b7f14c880ca0000000000000000000000000000000000000000000000000000000060005260046000fd5b60405160ff7f000000000000000000000000000000000000000000000000000000000000000016602082015260208152613e0d604082613d4c565b81519192911561505e576fffffffffffffffffffffffffffffffff6040840151166fffffffffffffffffffffffffffffffff60208501511610614ffb576146b891925b805182547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690151560a01b74ff0000000000000000000000000000000000000000161782556020810151825460409290920151608090811b7fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff9290921691821760018501557fffffffffffffffffffffffff0000000000000000000000000000000000000000909216174290911b73ffffffff0000000000000000000000000000000016179055565b60648361505c604051917f8020d12400000000000000000000000000000000000000000000000000000000835260048301906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565bfd5b6fffffffffffffffffffffffffffffffff604084015116158015906150ed575b61508c576146b89192614f1f565b60648361505c604051917fd68af9cc00000000000000000000000000000000000000000000000000000000835260048301906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565b506fffffffffffffffffffffffffffffffff602084015116151561507e565b906127109167ffffffffffffffff6151266020830161457e565b166000908152600b60205260409020917fffffffff00000000000000000000000000000000000000000000000000000000161561517757606061ffff615173935460901c16910135613ef9565b0490565b606061ffff615173935460801c16910135613ef9565b91908201809211613f0c57565b7fffffffff000000000000000000000000000000000000000000000000000000008116908115615271577dffff000000000000000000000000000000000000000000000000000000008116156152685760ff60015b169060f01c80615232575b506001036152055750565b7fc512f96c0000000000000000000000000000000000000000000000000000000060005260045260246000fd5b60005b6010811061524357506151fa565b6001811b8216615256575b600101615235565b9160018101809111613f0c579161524e565b60ff60006151ef565b5050565b9167ffffffffffffffff7f50f6fbee3ceedce6b7fd7eaef18244487867e6718aec7208187efb6b7908c14c9216928360005260086020526152be81836002604060002001615846565b6040805173ffffffffffffffffffffffffffffffffffffffff909216825260208201929092529081908101614d2b565b91909167ffffffffffffffff83169283600052600560205260ff60406000205460a01c16156153535750907fc6735cd4fa2bbe7b203b1682936e6ee61bc1702464bbbd12abb6630229d9a5f9918360005260056020526152be81836040600020615846565b906146b89350615275565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe182360301811215613ae357016020813591019167ffffffffffffffff8211613ae3578136038313613ae357565b9167ffffffffffffffff7fff0133389f9bb82d5b9385826160eaf2328039f6fa950eeb8cf0836da81789449216928360005260086020526152be81836040600020615846565b91909167ffffffffffffffff83169283600052600460205260ff60406000205460a01c16156154595750907f28d6c52e2b0b7587b0d195539fbe6af984b28791aca4d2cc0844244e38bce29e918360005260046020526152be81836040600020615846565b906146b893506153ae565b906040519182815491828252602082019060005260206000209260005b8181106154965750506146b892500383613d4c565b8454835260019485019487945060209093019201615481565b80548210156145fd5760005260206000200190600090565b6000818152600760205260409020548015615656577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818111613f0c57600654907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201918211613f0c578181036155e7575b50505060065480156155b8577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff016155758160066154af565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82549160031b1b19169055600655600052600760205260006040812055600190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b61563e6155f86156099360066154af565b90549060031b1c92839260066154af565b81939154907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060031b92831b921b19161790565b9055600052600760205260406000205538808061553c565b5050600090565b9060018201918160005282602052604060002054801515600014615788577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818111613f0c578254907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201918211613f0c57818103615751575b505050805480156155b8577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff019061571282826154af565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82549160031b1b191690555560005260205260006040812055600190565b61577161576161560993866154af565b90549060031b1c928392866154af565b9055600052836020526040600020553880806156da565b50505050600090565b806000526007602052604060002054156000146157eb5760065468010000000000000000811015613cc9576157d261560982600185940160065560066154af565b9055600654906000526007602052604060002055600190565b50600090565b60008281526001820160205260409020546156565780549068010000000000000000821015613cc9578261582f6156098460018096018555846154af565b905580549260005201602052604060002055600190565b9182549060ff8260a01c16158015615afa575b615af4576fffffffffffffffffffffffffffffffff8216916001850190815461589e63ffffffff6fffffffffffffffffffffffffffffffff83169360801c1642613f74565b9081615a56575b5050848110615a0a57508383106158ff5750506158d46fffffffffffffffffffffffffffffffff928392613f74565b16167fffffffffffffffffffffffffffffffff00000000000000000000000000000000825416179055565b9190915460801c92831561599e578161591791613f74565b927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810190808211613f0c5761596561596a9273ffffffffffffffffffffffffffffffffffffffff9661518d565b613f3b565b7fd0c8d23a000000000000000000000000000000000000000000000000000000006000526004526024521660445260646000fd5b73ffffffffffffffffffffffffffffffffffffffff83837fd0c8d23a000000000000000000000000000000000000000000000000000000006000527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6004526024521660445260646000fd5b828573ffffffffffffffffffffffffffffffffffffffff927f1a76572a000000000000000000000000000000000000000000000000000000006000526004526024521660445260646000fd5b828692939611615aca57615a7192614bae9160801c90613ef9565b80841015615ac55750825b85547fffffffffffffffffffffffff00000000ffffffffffffffffffffffffffffffff164260801b73ffffffff00000000000000000000000000000000161786559238806158a5565b615a7c565b7f9725942a0000000000000000000000000000000000000000000000000000000060005260046000fd5b50505050565b50821561585956fea164736f6c634300081a000a' as const +// generate:end diff --git a/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/burn-with-from-mint-token-pool.ts b/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/burn-with-from-mint-token-pool.ts new file mode 100644 index 000000000..f587ebf7c --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/burn-with-from-mint-token-pool.ts @@ -0,0 +1,4 @@ +export default // generate: +// `'${require('fs').readFileSync(require('module').createRequire(`${process.cwd()}/package.json`).resolve('@chainlink/contracts-ccip/bytecode/v2_0_0/burn_with_from_mint_token_pool.bin'), 'utf8').trim()}' as const` +'0x60e080604052346102aa5760a081615ee1803803809161001f82856102fb565b8339810103126102aa5780516001600160a01b03811691908290036102aa5761004a60208201610334565b61005660408301610342565b9061006f608061006860608601610342565b9401610342565b9233156102ea57600180546001600160a01b03191633179055841580156102d9575b80156102c8575b6102b7578460805260c052308403610220575b60a052600380546001600160a01b039283166001600160a01b0319918216179091556002805493909216921691909117905560405163095ea7b360e01b60208083019182523060248401526000196044808501919091528352906000906101136064856102fb565b83519082865af16000513d82610204575b5050156101bf575b604051615b2390816103be823960805181818161023e01528181610491015281816122700152818161244801528181612ab501528181612cb0015281816131a20152818161374f01526137a9015260a05181818161361501528181614928015281816149720152614ebc015260c0518181816102d9015281816113f50152818161230a01528181612b50015261323d0152f35b6101fd916101f860405163095ea7b360e01b602082015230602482015260006044820152604481526101f26064826102fb565b82610356565b610356565b388061012c565b9091506102185750813b15155b3880610124565b600114610211565b60405163313ce56760e01b8152602081600481885afa60009181610276575b5061024b575b506100ab565b60ff1660ff821681810361025f5750610245565b6332ad3e0760e11b60005260045260245260446000fd5b9091506020813d6020116102af575b81610292602093836102fb565b810103126102aa576102a390610334565b903861023f565b600080fd5b3d9150610285565b630a64406560e11b60005260046000fd5b506001600160a01b03811615610098565b506001600160a01b03841615610091565b639b15e16f60e01b60005260046000fd5b601f909101601f19168101906001600160401b0382119082101761031e57604052565b634e487b7160e01b600052604160045260246000fd5b519060ff821682036102aa57565b51906001600160a01b03821682036102aa57565b906000602091828151910182855af1156103b1576000513d6103a857506001600160a01b0381163b155b6103875750565b635274afe760e01b60009081526001600160a01b0391909116600452602490fd5b60011415610380565b6040513d6000823e3d90fdfe608080604052600436101561001357600080fd5b600090813560e01c90816301ffc9a7146139ca5750806306b859ef146138e5578063181f5a77146138845780631826b1e7146137cd57806321df0da71461377c578063240028e8146137185780632422ac451461363957806324f65ee7146135fb5780632cab0fb61461310757806337a3210d146130d35780633907753714612a0a5780634c5ef0ed146129c357806362ddd3c41461293c5780637437ff9f146128ee57806379ba5097146128275780638926f54f146127e15780638da5cb5b146127ad5780639a4575b9146121f7578063a42a7b8b14612090578063acfecf9114611f98578063ae39a25714611e0d578063b6cfa3b714611d52578063b794658014611d1a578063bfeffd3f14611c6e578063c4bffe2b14611b43578063c7230a601461189d578063dc04fa1f14611419578063dc0bd971146113c8578063dcbd41bc146111c4578063e8a1da1714610ae8578063ea6396db146109aa578063ec6ae7a714610967578063f2fde38b146108985763fbc801a71461019757600080fd5b346105db5760606003193601126105db576004359067ffffffffffffffff82116105db578160040160a060031984360301126105e9576101d5613afc565b9060443567ffffffffffffffff811161070f57906101fa610217923690600401613c27565b92906102046145e4565b5061020f8584615120565b933691613da1565b92608486019361022685614571565b73ffffffffffffffffffffffffffffffffffffffff807f00000000000000000000000000000000000000000000000000000000000000001691160361084e57602487019677ffffffffffffffff0000000000000000000000000000000061028c89614592565b60801b16604051907f2cbc26bb000000000000000000000000000000000000000000000000000000008252600482015260208160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa9081156107c157889161081f575b506107f75767ffffffffffffffff61032089614592565b16610338816000526007602052604060002054151590565b156107cc57602073ffffffffffffffffffffffffffffffffffffffff60025416916024604051809481937fa8d87a3b00000000000000000000000000000000000000000000000000000000835260048301525afa80156107c1578890610770575b73ffffffffffffffffffffffffffffffffffffffff9150163303610744576064810135936103c78686613f88565b7fffffffff00000000000000000000000000000000000000000000000000000000851694851561072257610423907fffffffff0000000000000000000000000000000000000000000000000000000060025460401b1690614a7c565b61043f816104308a614571565b6104398d614592565b90615408565b73ffffffffffffffffffffffffffffffffffffffff6003541693846105ed575b5050505050509061046f91613f88565b9161047984614592565b5073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016803b156105e9576040517f9dc29fac000000000000000000000000000000000000000000000000000000008152306004820152602481018590529082908290604490829084905af180156105de576105c6575b6105bc8461058b61058688877ff33bc26b4413b0e7f19f1ea739fdf99098c0061f1f87d954b11f5293fad9ae1067ffffffffffffffff61054c61054685614592565b93614571565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252336020830152810188905292169180606081015b0390a2614592565b614755565b90610594614eb5565b604051926105a184613d0c565b83526020830152604051928392604084526040840190613e69565b9060208301520390f35b6105d1828092613d60565b6105db5780610504565b80fd5b6040513d84823e3d90fd5b5080fd5b843b1561071e578994928b9694928692604051988997889687957fa8027c0f00000000000000000000000000000000000000000000000000000000875260048701608090528061063c91615372565b6084880160a0905261012488019061065392613fb6565b9261065d90613c12565b67ffffffffffffffff1660a487015260440161067890613bc3565b73ffffffffffffffffffffffffffffffffffffffff1660c48601528d8c60e48701526106a390613bc3565b73ffffffffffffffffffffffffffffffffffffffff1661010486015260248501528381036003190160448501526106d991613c55565b90606483015203925af18015610713579085916106fa575b8080808061045f565b8161070491613d60565b61070f5783386106f1565b8380fd5b6040513d87823e3d90fd5b8980fd5b5061073f816107308a614571565b6107398d614592565b906153c2565b61043f565b6024877f728fe07b00000000000000000000000000000000000000000000000000000000815233600452fd5b506020813d6020116107b9575b8161078a60209383613d60565b810103126107b5576107b073ffffffffffffffffffffffffffffffffffffffff91613f95565b610399565b8780fd5b3d915061077d565b6040513d8a823e3d90fd5b7fa9902c7e000000000000000000000000000000000000000000000000000000008852600452602487fd5b6004877f53ad11d8000000000000000000000000000000000000000000000000000000008152fd5b610841915060203d602011610847575b6108398183613d60565b810190614be8565b38610309565b503d61082f565b60248673ffffffffffffffffffffffffffffffffffffffff61086f88614571565b7f961c9a4f00000000000000000000000000000000000000000000000000000000835216600452fd5b50346105db5760206003193601126105db5773ffffffffffffffffffffffffffffffffffffffff6108c7613b5a565b6108cf614c00565b1633811461093f57807fffffffffffffffffffffffff000000000000000000000000000000000000000083541617825573ffffffffffffffffffffffffffffffffffffffff600154167fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12788380a380f35b6004827fdad89dca000000000000000000000000000000000000000000000000000000008152fd5b50346105db57806003193601126105db5760207fffffffff0000000000000000000000000000000000000000000000000000000060025460401b16604051908152f35b50346105db5760806003193601126105db576109c4613b5a565b506109cd613be4565b6109d5613b2b565b5060643567ffffffffffffffff8111610ae4579167ffffffffffffffff604092610a0560e0953690600401613c27565b50508260c08551610a1581613d44565b82815282602082015282878201528260608201528260808201528260a08201520152168152600b6020522060405190610a4d82613d44565b5461ffff818163ffffffff82169485815263ffffffff60208201818560201c1681528160408401818760401c168152816060860193818960601c16855260ff60c060808901988a8c60801c168a528a60a082019c60901c168c52019b60a01c1615158b526040519b8c52511660208b0152511660408901525116606087015251166080850152511660a083015251151560c0820152f35b8280fd5b50346105db5760406003193601126105db5760043567ffffffffffffffff81116105e957610b1a903690600401613e93565b9060243567ffffffffffffffff811161070f5790610b3d84923690600401613e93565b939091610b48614c00565b83905b8282106110055750505081927ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee182360301935b81811015611001578060051b83013585811215610ffd57830161012081360312610ffd5760405194610baf86613d28565b610bb882613c12565b8652602082013567ffffffffffffffff81116105e95782019436601f870112156105e957853595610be887613ef5565b96610bf66040519889613d60565b80885260208089019160051b83010190368211610ffd5760208301905b828210610fca575050505060208701958652604083013567ffffffffffffffff8111610ae457610c469036908501613e06565b9160408801928352610c70610c5e3660608701614801565b9460608a0195865260c0369101614801565b956080890196875283515115610fa257610c9467ffffffffffffffff8a51166157a5565b15610f6b5767ffffffffffffffff8951168252600860205260408220610cbb865182614ef0565b610cc9885160028301614ef0565b6004855191019080519067ffffffffffffffff8211610f3e57610cec8354614640565b601f8111610f03575b50602090601f8311600114610e6457610d439291869183610e59575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b90555b815b88518051821015610d7d5790610d77600192610d708367ffffffffffffffff8f5116926145fd565b5190614c4b565b01610d48565b5050977f8d340f17e19058004c20453540862a9c62778504476f6756755cb33bcd6c38c2939199975095610e4b67ffffffffffffffff6001979694985116925193519151610e17610de260405196879687526101006020880152610100870190613c55565b9360408601906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565b60a08401906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565b0390a1019392909193610b7e565b015190508e80610d11565b83865281862091907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08416875b818110610eeb5750908460019594939210610eb4575b505050811b019055610d46565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558d8080610ea7565b92936020600181928786015181550195019301610e91565b610f2e9084875260208720601f850160051c81019160208610610f34575b601f0160051c019061489d565b8d610cf5565b9091508190610f21565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b60248267ffffffffffffffff8b51167f1d5ad3c5000000000000000000000000000000000000000000000000000000008252600452fd5b6004827f14c880ca000000000000000000000000000000000000000000000000000000008152fd5b813567ffffffffffffffff8111610ff957602091610fee8392833691890101613e06565b815201910190610c13565b8680fd5b8480fd5b8380f35b9267ffffffffffffffff6110276110228486889a9699979a6147d4565b614592565b1691611032836154db565b1561119857828452600860205261104e60056040862001615478565b94845b865181101561108757600190858752600860205261108060056040892001611079838b6145fd565b5190615671565b5001611051565b50939692909450949094808752600860205260056040882088815588600182015588600282015588600382015588600482016110c38154614640565b80611157575b5050500180549088815581611139575b5050907f5204aec90a3c794d8e90fded8b46ae9c7c552803e7e832e0c1d358396d8599166020836001948a52600482528985604082208281550155808a52600582528985604082208281550155604051908152a101909194939294610b4b565b885260208820908101905b818110156110d957888155600101611144565b601f811160011461116d5750555b888a806110c9565b8183526020832061118891601f01861c81019060010161489d565b8082528160208120915555611165565b602484847f1e670e4b000000000000000000000000000000000000000000000000000000008252600452fd5b50346105db5760206003193601126105db5760043567ffffffffffffffff81116105e9576111f6903690600401613ec4565b73ffffffffffffffffffffffffffffffffffffffff600a5416331415806113a6575b61137a57825b818110611229578380f35b611234818385614777565b67ffffffffffffffff61124682614592565b169061125f826000526007602052604060002054151590565b1561134e57907f41f7c8f7cfdad9350aa495e6c54cbbf750a07ab38a9098aed1256e30dd1682bb60e08361130e6112e8602060019897018b6112a082614787565b156113155787905260046020526112c760408d206112c13660408801614801565b90614ef0565b868c5260056020526112e360408d206112c13660a08801614801565b614787565b9160405192151583526113016020840160408301614859565b60a0608084019101614859565ba20161121e565b60026040828a6112e3945260086020526113378282206112c136858c01614801565b8a8152600860205220016112c13660a08801614801565b602486837f1e670e4b000000000000000000000000000000000000000000000000000000008252600452fd5b6024837f8e4a23d600000000000000000000000000000000000000000000000000000000815233600452fd5b5073ffffffffffffffffffffffffffffffffffffffff60015416331415611218565b50346105db57806003193601126105db57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50346105db5760406003193601126105db5760043567ffffffffffffffff81116105e95761144b903690600401613ec4565b60243567ffffffffffffffff811161070f5761146b903690600401613e93565b919092611476614c00565b845b8281106114e257505050825b81811061148f578380f35b8067ffffffffffffffff6114a961102260019486886147d4565b16808652600b6020528560408120557f5479bbc0288b7eaeaf2ace0943b88016cc648964fcd42919a86fd93b15fdbee88680a201611484565b67ffffffffffffffff6114f9611022838686614777565b16611511816000526007602052604060002054151590565b1561187257611521828585614777565b602081019060e081019061153482614787565b156118465760a0810161271061ffff61154c83614794565b1610156118375760c082019161271061ffff61156785614794565b1610156117ff5763ffffffff61157c866147a3565b16156117d357858c52600b60205260408c20611597866147a3565b63ffffffff169080549060408401916115af836147a3565b60201b67ffffffff00000000169360608601946115cb866147a3565b60401b6bffffffff00000000000000001696608001966115ea886147a3565b60601b6fffffffff00000000000000000000000016916116098a614794565b60801b71ffff00000000000000000000000000000000169361162a8c614794565b60901b73ffff00000000000000000000000000000000000016957fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016177fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff16177fffffffffffffffffffffffffffff0000ffffffffffffffffffffffffffffffff16177fffffffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffff1617171781556116dd87614787565b81547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690151560a01b74ff0000000000000000000000000000000000000000161790556040519661172e906147b4565b63ffffffff16875261173f906147b4565b63ffffffff166020870152611753906147b4565b63ffffffff166040860152611767906147b4565b63ffffffff16606085015261177b906147c5565b61ffff16608084015261178d906147c5565b61ffff1660a083015261179f90613cb4565b151560c082015260e07ffae1e296719dac5269c3886fb5002bb29bf17ae403060c6eb063a55abaaa104191a2600101611478565b60248c877f12332265000000000000000000000000000000000000000000000000000000008252600452fd5b60248c61ffff61180e86614794565b7f95f3517a00000000000000000000000000000000000000000000000000000000835216600452fd5b8a61ffff61180e602493614794565b60248a857f12332265000000000000000000000000000000000000000000000000000000008252600452fd5b7f1e670e4b000000000000000000000000000000000000000000000000000000008752600452602486fd5b50346105db5760406003193601126105db5760043567ffffffffffffffff81116105e9576118cf903690600401613e93565b906118d8613ba0565b9173ffffffffffffffffffffffffffffffffffffffff6001541633141580611b21575b611af55773ffffffffffffffffffffffffffffffffffffffff8316908115611acd57845b81811061192a578580f35b73ffffffffffffffffffffffffffffffffffffffff61195261194d8385886147d4565b614571565b166040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152602081602481855afa9081156107c1578891611a9a575b50806119a7575b505060010161191f565b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000602080830191825273ffffffffffffffffffffffffffffffffffffffff8a16602484015260448084018590528352918a9190611a08606482613d60565b519082865af115611a8f5787513d611a865750813b155b611a5a5790847f508d7d183612c18fc339b42618912b9fa3239f631dd7ec0671f950200a0fa66e602060019594604051908152a3903861199d565b602488837f5274afe7000000000000000000000000000000000000000000000000000000008252600452fd5b60011415611a1f565b6040513d89823e3d90fd5b905060203d8111611ac6575b611ab08183613d60565b602082600092810103126105db57505138611996565b503d611aa6565b6004857f8579befe000000000000000000000000000000000000000000000000000000008152fd5b6024847fcb1afbd700000000000000000000000000000000000000000000000000000000815233600452fd5b5073ffffffffffffffffffffffffffffffffffffffff600c54163314156118fb565b50346105db57806003193601126105db57604051906006548083528260208101600684526020842092845b818110611c55575050611b8392500383613d60565b8151611ba7611b9182613ef5565b91611b9f6040519384613d60565b808352613ef5565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602083019301368437805b8451811015611c06578067ffffffffffffffff611bf3600193886145fd565b5116611bff82866145fd565b5201611bd4565b50925090604051928392602084019060208552518091526040840192915b818110611c32575050500390f35b825167ffffffffffffffff16845285945060209384019390920191600101611c24565b8454835260019485019487945060209093019201611b6e565b50346105db5760206003193601126105db5760043573ffffffffffffffffffffffffffffffffffffffff81168091036105e957611ca9614c00565b7fffffffffffffffffffffffff00000000000000000000000000000000000000006003547fbaff46844acf36d6ee996f489a1a288709c4542bd33cd557770afd267d5812096040805173ffffffffffffffffffffffffffffffffffffffff84168152856020820152a1161760035580f35b50346105db5760206003193601126105db57611d4e611d3a610586613bfb565b604051918291602083526020830190613c55565b0390f35b50346105db5760206003193601126105db577f307cf716eade81675bea3ccb6917b0f91baa2160056765d9a83d76f819caf06a6020611d8f613ac8565b611d97614c00565b6002547fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff77ffffffff0000000000000000000000000000000000000000808460401c16169116176002557fffffffff0000000000000000000000000000000000000000000000000000000060405191168152a180f35b50346105db5760606003193601126105db57611e27613b5a565b90611e30613ba0565b6044359273ffffffffffffffffffffffffffffffffffffffff841680850361070f57611e5a614c00565b73ffffffffffffffffffffffffffffffffffffffff82168015611f705794611f6a917f3f1036e85d016a93254a0b1415844f79b85424959d90ae5ad51ce8f4533fe70195967fffffffffffffffffffffffff0000000000000000000000000000000000000000600254161760025573ffffffffffffffffffffffffffffffffffffffff85167fffffffffffffffffffffffff0000000000000000000000000000000000000000600a541617600a557fffffffffffffffffffffffff0000000000000000000000000000000000000000600c541617600c556040519384938491604091949373ffffffffffffffffffffffffffffffffffffffff809281606087019816865216602085015216910152565b0390a180f35b6004857f14c880ca000000000000000000000000000000000000000000000000000000008152fd5b50346105db5767ffffffffffffffff611fb036613e24565b929091611fbb614c00565b1691611fd4836000526007602052604060002054151590565b1561119857828452600860205261200360056040862001611ff6368486613da1565b6020815191012090615671565b1561204857907f52d00ee4d9bd51b40168f2afc5848837288ce258784ad914278791464b3f4d7691612042604051928392602084526020840191613fb6565b0390a280f35b8261208c836040519384937f74f23c7c0000000000000000000000000000000000000000000000000000000085526004850152604060248501526044840191613fb6565b0390fd5b50346105db5760206003193601126105db5767ffffffffffffffff6120b3613bfb565b16815260086020526120ca60056040832001615478565b80517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061210f6120f983613ef5565b926121076040519485613d60565b808452613ef5565b01835b8181106121e6575050825b82518110156121635780612133600192856145fd565b518552600960205261214760408620614693565b61215182856145fd565b5261215c81846145fd565b500161211d565b81846040519182916020830160208452825180915260408401602060408360051b870101940192905b82821061219b57505050500390f35b919360206121d6827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc060019597998495030186528851613c55565b960192019201859493919261218c565b806060602080938601015201612112565b50346105db5760206003193601126105db5760043567ffffffffffffffff81116105e957806004019060a06003198236030112610ae4576122366145e4565b506040516020936122478583613d60565b808252608483019161225883614571565b73ffffffffffffffffffffffffffffffffffffffff807f00000000000000000000000000000000000000000000000000000000000000001691160361278c57602484019477ffffffffffffffff000000000000000000000000000000006122be87614592565b60801b16604051907f2cbc26bb0000000000000000000000000000000000000000000000000000000082526004820152878160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa90811561271157849161276f575b506127475767ffffffffffffffff61235187614592565b16612369816000526007602052604060002054151590565b1561271c578773ffffffffffffffffffffffffffffffffffffffff60025416916024604051809481937fa8d87a3b00000000000000000000000000000000000000000000000000000000835260048301525afa80156127115784906126c9575b73ffffffffffffffffffffffffffffffffffffffff915016330361269d57606485013594612403866123fa87614571565b6107398a614592565b73ffffffffffffffffffffffffffffffffffffffff600354169182612580575b5050505061243084614592565b5073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016803b156105e9576040517f9dc29fac000000000000000000000000000000000000000000000000000000008152306004820152602481018590529082908290604490829084905af180156105de5761256b575b8561253b61058687877ff33bc26b4413b0e7f19f1ea739fdf99098c0061f1f87d954b11f5293fad9ae1067ffffffffffffffff8961057e6125046124fe87614592565b92614571565b6040805173ffffffffffffffffffffffffffffffffffffffff90921682523360208301528101959095529116929081906060820190565b90612544614eb5565b6040519261255184613d0c565b835281830152611d4e604051928284938452830190613e69565b612576828092613d60565b6105db57806124bb565b823b15610ffd57918791858094604051968795869485937fa8027c0f0000000000000000000000000000000000000000000000000000000085526004850160809052806125cc91615372565b6084860160a090526101248601906125e392613fb6565b916125ed90613c12565b67ffffffffffffffff1660a485015260440161260890613bc3565b73ffffffffffffffffffffffffffffffffffffffff1660c48401528b60e48401526126328b613bc3565b73ffffffffffffffffffffffffffffffffffffffff1661010484015283602484015282810360031901604484015261266991613c55565b8a606483015203925af180156105de57908291612688575b8080612423565b8161269291613d60565b6105db578038612681565b6024837f728fe07b00000000000000000000000000000000000000000000000000000000815233600452fd5b508781813d831161270a575b6126df8183613d60565b8101031261070f5761270573ffffffffffffffffffffffffffffffffffffffff91613f95565b6123c9565b503d6126d5565b6040513d86823e3d90fd5b7fa9902c7e000000000000000000000000000000000000000000000000000000008452600452602483fd5b6004837f53ad11d8000000000000000000000000000000000000000000000000000000008152fd5b6127869150883d8a11610847576108398183613d60565b3861233a565b5073ffffffffffffffffffffffffffffffffffffffff61086f602493614571565b50346105db57806003193601126105db57602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b50346105db5760206003193601126105db57602061281d67ffffffffffffffff612809613bfb565b166000526007602052604060002054151590565b6040519015158152f35b50346105db57806003193601126105db57805473ffffffffffffffffffffffffffffffffffffffff811633036128c6577fffffffffffffffffffffffff000000000000000000000000000000000000000060015491338284161760015516825573ffffffffffffffffffffffffffffffffffffffff3391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b6004827f02b543c6000000000000000000000000000000000000000000000000000000008152fd5b50346105db57806003193601126105db57600254600a54600c546040805173ffffffffffffffffffffffffffffffffffffffff94851681529284166020840152921691810191909152606090f35b50346105db5761294b36613e24565b61295793929193614c00565b67ffffffffffffffff8216612979816000526007602052604060002054151590565b156129985750612995929361298f913691613da1565b90614c4b565b80f35b7f1e670e4b000000000000000000000000000000000000000000000000000000008452600452602483fd5b50346105db5760406003193601126105db576129dd613bfb565b906024359067ffffffffffffffff82116105db57602061281d84612a043660048701613e06565b906145a7565b50346105db5760206003193601126105db576004359067ffffffffffffffff82116105db57816004019061010060031984360301126105db5780604051612a5081613cc1565b5280604051612a5e81613cc1565b52606483013560c4840193612a8e612a88612a83612a7c8888614520565b3691613da1565b6148b4565b8361496f565b936084820195612a9d87614571565b73ffffffffffffffffffffffffffffffffffffffff807f0000000000000000000000000000000000000000000000000000000000000000169116036130b257602483019377ffffffffffffffff00000000000000000000000000000000612b0386614592565b60801b16604051907f2cbc26bb000000000000000000000000000000000000000000000000000000008252600482015260208160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa908115611a8f578791613093575b5061306b5767ffffffffffffffff612b9786614592565b16612baf816000526007602052604060002054151590565b1561304057602073ffffffffffffffffffffffffffffffffffffffff60025416916044604051809481937f83826b2b00000000000000000000000000000000000000000000000000000000835260048301523360248301525afa908115611a8f578791613021575b5015612ff557612c2685614592565b92612c3c60a4860194612a04612a7c8785614520565b15612fae57612c5d88612c4e8b614571565b612c5789614592565b90615289565b73ffffffffffffffffffffffffffffffffffffffff600354169283612de0575b505050505060440191612c8f83614571565b612c9883614592565b5073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690813b15610ae4576040517f40c10f1900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff919091166004820152602481018690529082908290604490829084905af180156105de57612dcb575b5050608067ffffffffffffffff60209573ffffffffffffffffffffffffffffffffffffffff612d97612d916105467ffc5e3a5bddc11d92c2dc20fae6f7d5eb989f056be35239f7de7e86150609abc097614592565b96614571565b816040519716875233898801521660408601528560608601521692a260405190612dc082613cc1565b815260405190518152f35b612dd6828092613d60565b6105db5780612d3c565b833b156107b557878795938195938c93604051988997889687957f6371157400000000000000000000000000000000000000000000000000000000875260048701606090528d612e308780615372565b60648a0161010090526101648a0190612e4892613fb6565b94612e5290613c12565b67ffffffffffffffff166084890152604401612e6d90613bc3565b73ffffffffffffffffffffffffffffffffffffffff1660a488015260c4870152612e9690613bc3565b73ffffffffffffffffffffffffffffffffffffffff1660e4860152612ebb9084615372565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c86840301610104870152612ef09291613fb6565b90612efb9083615372565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c85840301610124860152612f309291613fb6565b9060e48a01612f3e91615372565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c84840301610144850152612f739291613fb6565b8b602483015282604483015203925af1801561271157908491612f99575b808080612c7d565b81612fa391613d60565b610ae4578238612f91565b83612fb891614520565b61208c6040519283927f24eb47e5000000000000000000000000000000000000000000000000000000008452602060048501526024840191613fb6565b6024867f728fe07b00000000000000000000000000000000000000000000000000000000815233600452fd5b61303a915060203d602011610847576108398183613d60565b38612c17565b7fa9902c7e000000000000000000000000000000000000000000000000000000008752600452602486fd5b6004867f53ad11d8000000000000000000000000000000000000000000000000000000008152fd5b6130ac915060203d602011610847576108398183613d60565b38612b80565b60248573ffffffffffffffffffffffffffffffffffffffff61086f8a614571565b50346105db57806003193601126105db57602073ffffffffffffffffffffffffffffffffffffffff60035416604051908152f35b50346105db5760406003193601126105db576004359067ffffffffffffffff82116105db57816004019061010060031984360301126105db57613148613afc565b918160405161315681613cc1565b5260648401359360c481019361317b613175612a83612a7c8887614520565b8761496f565b94608483019661318a88614571565b73ffffffffffffffffffffffffffffffffffffffff807f0000000000000000000000000000000000000000000000000000000000000000169116036135da57602484019477ffffffffffffffff000000000000000000000000000000006131f087614592565b60801b16604051907f2cbc26bb000000000000000000000000000000000000000000000000000000008252600482015260208160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa9081156107c15788916135bb575b506107f75767ffffffffffffffff61328487614592565b1661329c816000526007602052604060002054151590565b156107cc57602073ffffffffffffffffffffffffffffffffffffffff60025416916044604051809481937f83826b2b00000000000000000000000000000000000000000000000000000000835260048301523360248301525afa9081156107c157889161359c575b50156107445761331386614592565b9361332960a4870195612a04612a7c8886614520565b15613592577fffffffff000000000000000000000000000000000000000000000000000000001690811561357757613373896133648c614571565b61336d8a614592565b90615302565b73ffffffffffffffffffffffffffffffffffffffff6003541693846133a6575b50505050505060440191612c8f83614571565b843b1561357357868995938c959387938b6040519a8b998a9889977f6371157400000000000000000000000000000000000000000000000000000000895260048901606090526133f68780615372565b60648b0161010090526101648b019061340e92613fb6565b9461341890613c12565b67ffffffffffffffff1660848a015260440161343390613bc3565b73ffffffffffffffffffffffffffffffffffffffff1660a489015260c488015261345c90613bc3565b73ffffffffffffffffffffffffffffffffffffffff1660e48701526134819084615372565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c878403016101048801526134b69291613fb6565b906134c19083615372565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c868403016101248701526134f69291613fb6565b9060e48b0161350491615372565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c858403016101448601526135399291613fb6565b908c6024840152604483015203925af180156127115761355e575b8080808080613393565b9261356c8160449395613d60565b9290613554565b8880fd5b61358d896135848c614571565b612c578a614592565b613373565b612fb88583614520565b6135b5915060203d602011610847576108398183613d60565b38613304565b6135d4915060203d602011610847576108398183613d60565b3861326d565b60248673ffffffffffffffffffffffffffffffffffffffff61086f8b614571565b50346105db57806003193601126105db57602060405160ff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50346105db5760406003193601126105db57613653613bfb565b6024359182151583036105db57610140613716613670858561449d565b6136c660409392935180946fffffffffffffffffffffffffffffffff6080809282815116855263ffffffff6020820151166020860152604081015115156040860152826060820151166060860152015116910152565b60a08301906fffffffffffffffffffffffffffffffff6080809282815116855263ffffffff6020820151166020860152604081015115156040860152826060820151166060860152015116910152565bf35b50346105db5760206003193601126105db57602090613735613b5a565b905073ffffffffffffffffffffffffffffffffffffffff807f0000000000000000000000000000000000000000000000000000000000000000169116146040519015158152f35b50346105db57806003193601126105db57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50346105db5760c06003193601126105db576137e7613b5a565b506137f0613be4565b6137f8613b7d565b50608435917fffffffff00000000000000000000000000000000000000000000000000000000831683036105db5760a4359067ffffffffffffffff82116105db5760a063ffffffff8061ffff61385d88886138563660048b01613c27565b50506142ed565b95926040979194975197885216602087015216604085015216606083015215156080820152f35b50346105db57806003193601126105db5750611d4e6040516138a7604082613d60565b601f81527f4275726e5769746846726f6d4d696e74546f6b656e506f6f6c20322e302e30006020820152604051918291602083526020830190613c55565b50346105db5760c06003193601126105db576138ff613b5a565b613907613be4565b906064357fffffffff000000000000000000000000000000000000000000000000000000008116810361070f5760843567ffffffffffffffff8111610ffd57613954903690600401613c27565b9160a435936002851015610ff95761396f9560443591613ff5565b90604051918291602083016020845282518091526020604085019301915b81811061399b575050500390f35b825173ffffffffffffffffffffffffffffffffffffffff1684528594506020938401939092019160010161398d565b9050346105e95760206003193601126105e9576020907fffffffff00000000000000000000000000000000000000000000000000000000613a09613ac8565b167faff2afbf000000000000000000000000000000000000000000000000000000008114908115613a9e575b8115613a74575b8115613a4a575b5015158152f35b7f01ffc9a70000000000000000000000000000000000000000000000000000000091501483613a43565b7f0e64dd290000000000000000000000000000000000000000000000000000000081149150613a3c565b7f940a15420000000000000000000000000000000000000000000000000000000081149150613a35565b600435907fffffffff0000000000000000000000000000000000000000000000000000000082168203613af757565b600080fd5b602435907fffffffff0000000000000000000000000000000000000000000000000000000082168203613af757565b604435907fffffffff0000000000000000000000000000000000000000000000000000000082168203613af757565b6004359073ffffffffffffffffffffffffffffffffffffffff82168203613af757565b6064359073ffffffffffffffffffffffffffffffffffffffff82168203613af757565b6024359073ffffffffffffffffffffffffffffffffffffffff82168203613af757565b359073ffffffffffffffffffffffffffffffffffffffff82168203613af757565b6024359067ffffffffffffffff82168203613af757565b6004359067ffffffffffffffff82168203613af757565b359067ffffffffffffffff82168203613af757565b9181601f84011215613af75782359167ffffffffffffffff8311613af75760208381860195010111613af757565b919082519283825260005b848110613c9f5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006020809697860101520116010190565b80602080928401015182828601015201613c60565b35908115158203613af757565b6020810190811067ffffffffffffffff821117613cdd57604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040810190811067ffffffffffffffff821117613cdd57604052565b60a0810190811067ffffffffffffffff821117613cdd57604052565b60e0810190811067ffffffffffffffff821117613cdd57604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117613cdd57604052565b92919267ffffffffffffffff8211613cdd5760405191613de9601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200184613d60565b829481845281830111613af7578281602093846000960137010152565b9080601f83011215613af757816020613e2193359101613da1565b90565b906040600319830112613af75760043567ffffffffffffffff81168103613af757916024359067ffffffffffffffff8211613af757613e6591600401613c27565b9091565b613e21916020613e828351604084526040840190613c55565b920151906020818403910152613c55565b9181601f84011215613af75782359167ffffffffffffffff8311613af7576020808501948460051b010111613af757565b9181601f84011215613af75782359167ffffffffffffffff8311613af7576020808501948460081b010111613af757565b67ffffffffffffffff8111613cdd5760051b60200190565b81810292918115918404141715613f2057565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8115613f59570490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b91908203918211613f2057565b519073ffffffffffffffffffffffffffffffffffffffff82168203613af757565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b92959390919473ffffffffffffffffffffffffffffffffffffffff600354169586156142cb578097600287101561429c5773ffffffffffffffffffffffffffffffffffffffff98614156957fffffffff0000000000000000000000000000000000000000000000000000000093896142725767ffffffffffffffff8216600052600b6020526040600020906040519161408d83613d44565b549163ffffffff8316815263ffffffff8360201c16602082015263ffffffff8360401c16604082015263ffffffff8360601c16606082015260c061ffff8460801c169182608082015260ff60a082019561ffff8160901c16875260a01c161515918291015261421e575b50505067ffffffffffffffff905b6040519b8c997f06b859ef000000000000000000000000000000000000000000000000000000008b521660048a0152166024880152604487015216606485015260c0608485015260c4840191613fb6565b928180600095869560a483015203915afa91821561421157819261417957505090565b9091503d8083833e61418b8183613d60565b810190602081830312610ae45780519067ffffffffffffffff821161070f570181601f82011215610ae4578051906141c282613ef5565b936141d06040519586613d60565b82855260208086019360051b8301019384116105db5750602001905b8282106141f95750505090565b6020809161420684613f95565b8152019101906141ec565b50604051903d90823e3d90fd5b92935067ffffffffffffffff928587161561425a575061271061424961ffff61425094511683613f0d565b0490613f88565b915b9038806140f7565b61426c92506142496127109183613f0d565b91614252565b67ffffffffffffffff91925061429690614290612a8336898b613da1565b9061496f565b91614105565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b50505050505050506040516142e1602082613d60565b60008152600036813790565b67ffffffffffffffff9092919261432b7fffffffff0000000000000000000000000000000000000000000000000000000060025460401b1685614a7c565b16600052600b60205260406000206040519061434682613d44565b549163ffffffff83169384835263ffffffff8460201c169384602085015263ffffffff8160401c169182604086015263ffffffff8260601c169081606087015261ffff8360801c169586608082015260ff61ffff8560901c16948560a084015260a01c16159060c082159101526143f3577fffffffff00000000000000000000000000000000000000000000000000000000166143e857505093929190600190565b959493509160019150565b5050505092505050600090600090600090600090600090565b6040519061441982613d28565b60006080838281528260208201528260408201528260608201520152565b9060405161444481613d28565b60806001829460ff81546fffffffffffffffffffffffffffffffff8116865263ffffffff81861c16602087015260a01c161515604085015201546fffffffffffffffffffffffffffffffff81166060840152811c910152565b67ffffffffffffffff916144af61440c565b506144b861440c565b506144ec57166000526008602052604060002090613e216144e060026144e56144e086614437565b614b63565b9401614437565b16908160005260046020526145076144e06040600020614437565b916000526005602052613e216144e06040600020614437565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215613af7570180359067ffffffffffffffff8211613af757602001918136038313613af757565b3573ffffffffffffffffffffffffffffffffffffffff81168103613af75790565b3567ffffffffffffffff81168103613af75790565b9067ffffffffffffffff613e2192166000526008602052600560406000200190602081519101209060019160005201602052604060002054151590565b604051906145f182613d0c565b60606020838281520152565b80518210156146115760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90600182811c92168015614689575b602083101461465a57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f169161464f565b90604051918260008254926146a784614640565b808452936001811690811561471557506001146146ce575b506146cc92500383613d60565b565b90506000929192526020600020906000915b8183106146f95750509060206146cc92820101386146bf565b60209193508060019154838589010152019101909184926146e0565b602093506146cc9592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091501682840152151560051b820101386146bf565b67ffffffffffffffff166000526008602052613e216004604060002001614693565b91908110156146115760081b0190565b358015158103613af75790565b3561ffff81168103613af75790565b3563ffffffff81168103613af75790565b359063ffffffff82168203613af757565b359061ffff82168203613af757565b91908110156146115760051b0190565b35906fffffffffffffffffffffffffffffffff82168203613af757565b9190826060910312613af7576040516060810181811067ffffffffffffffff821117613cdd57604052604061485481839561483b81613cb4565b8552614849602082016147e4565b6020860152016147e4565b910152565b6fffffffffffffffffffffffffffffffff6148976040809361487a81613cb4565b151586528361488b602083016147e4565b166020870152016147e4565b16910152565b8181106148a8575050565b6000815560010161489d565b80518015614924576020036148e6578051602082810191830183900312613af757519060ff82116148e6575060ff1690565b61208c906040519182917f953576f7000000000000000000000000000000000000000000000000000000008352602060048401526024830190613c55565b50507f000000000000000000000000000000000000000000000000000000000000000090565b9060ff8091169116039060ff8211613f2057565b60ff16604d8111613f2057600a0a90565b907f00000000000000000000000000000000000000000000000000000000000000009060ff82169060ff811692828414614a7557828411614a4b57906149b49161494a565b91604d60ff8416118015614a12575b6149dc575050906149d6613e219261495e565b90613f0d565b9091507fa9cb113d0000000000000000000000000000000000000000000000000000000060005260045260245260445260646000fd5b50614a1c8361495e565b8015613f59577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0484116149c3565b614a549161494a565b91604d60ff8416116149dc57505090614a6f613e219261495e565b90613f4f565b5050505090565b7fffffffff000000000000000000000000000000000000000000000000000000008116908115614b5e57614aaf816151ae565b7dffff00000000000000000000000000000000000000000000000000000000601082811c9085901c1616614b5e5761ffff8360e01c168015918215614b4d575b5050614af9575050565b7fffffffff0000000000000000000000000000000000000000000000000000000092507fdf63778f000000000000000000000000000000000000000000000000000000006000526004521660245260446000fd5b60e01c61ffff161090503880614aef565b505050565b614b6b61440c565b506fffffffffffffffffffffffffffffffff6060820151166fffffffffffffffffffffffffffffffff8083511691614bc86020850193614bc2614bb563ffffffff87511642613f88565b8560808901511690613f0d565b906151a1565b80821015614be157505b16825263ffffffff4216905290565b9050614bd2565b90816020910312613af757518015158103613af75790565b73ffffffffffffffffffffffffffffffffffffffff600154163303614c2157565b7f2b5c74de0000000000000000000000000000000000000000000000000000000060005260046000fd5b90805115614e8b5767ffffffffffffffff81516020830120921691826000526008602052614c80816005604060002001615805565b15614e475760005260096020526040600020815167ffffffffffffffff8111613cdd57614cad8254614640565b601f8111614e15575b506020601f8211600114614d4f5791614d29827f7d628c9a1796743d365ab521a8b2a4686e419b3269919dc9145ea2ce853b54ea9593614d3f95600091614d44575b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b9055604051918291602083526020830190613c55565b0390a2565b905084015138614cf8565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082169083600052806000209160005b818110614dfd575092614d3f9492600192827f7d628c9a1796743d365ab521a8b2a4686e419b3269919dc9145ea2ce853b54ea989610614dc6575b5050811b019055611d3a565b8501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690553880614dba565b9192602060018192868a015181550194019201614d7f565b614e4190836000526020600020601f840160051c81019160208510610f3457601f0160051c019061489d565b38614cb6565b509061208c6040519283927f393b8ad20000000000000000000000000000000000000000000000000000000084526004840152604060248401526044830190613c55565b7f14c880ca0000000000000000000000000000000000000000000000000000000060005260046000fd5b60405160ff7f000000000000000000000000000000000000000000000000000000000000000016602082015260208152613e21604082613d60565b815191929115615072576fffffffffffffffffffffffffffffffff6040840151166fffffffffffffffffffffffffffffffff6020850151161061500f576146cc91925b805182547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690151560a01b74ff0000000000000000000000000000000000000000161782556020810151825460409290920151608090811b7fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff9290921691821760018501557fffffffffffffffffffffffff0000000000000000000000000000000000000000909216174290911b73ffffffff0000000000000000000000000000000016179055565b606483615070604051917f8020d12400000000000000000000000000000000000000000000000000000000835260048301906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565bfd5b6fffffffffffffffffffffffffffffffff60408401511615801590615101575b6150a0576146cc9192614f33565b606483615070604051917fd68af9cc00000000000000000000000000000000000000000000000000000000835260048301906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565b506fffffffffffffffffffffffffffffffff6020840151161515615092565b906127109167ffffffffffffffff61513a60208301614592565b166000908152600b60205260409020917fffffffff00000000000000000000000000000000000000000000000000000000161561518b57606061ffff615187935460901c16910135613f0d565b0490565b606061ffff615187935460801c16910135613f0d565b91908201809211613f2057565b7fffffffff000000000000000000000000000000000000000000000000000000008116908115615285577dffff0000000000000000000000000000000000000000000000000000000081161561527c5760ff60015b169060f01c80615246575b506001036152195750565b7fc512f96c0000000000000000000000000000000000000000000000000000000060005260045260246000fd5b60005b60108110615257575061520e565b6001811b821661526a575b600101615249565b9160018101809111613f205791615262565b60ff6000615203565b5050565b9167ffffffffffffffff7f50f6fbee3ceedce6b7fd7eaef18244487867e6718aec7208187efb6b7908c14c9216928360005260086020526152d28183600260406000200161585a565b6040805173ffffffffffffffffffffffffffffffffffffffff909216825260208201929092529081908101614d3f565b91909167ffffffffffffffff83169283600052600560205260ff60406000205460a01c16156153675750907fc6735cd4fa2bbe7b203b1682936e6ee61bc1702464bbbd12abb6630229d9a5f9918360005260056020526152d28183604060002061585a565b906146cc9350615289565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe182360301811215613af757016020813591019167ffffffffffffffff8211613af7578136038313613af757565b9167ffffffffffffffff7fff0133389f9bb82d5b9385826160eaf2328039f6fa950eeb8cf0836da81789449216928360005260086020526152d28183604060002061585a565b91909167ffffffffffffffff83169283600052600460205260ff60406000205460a01c161561546d5750907f28d6c52e2b0b7587b0d195539fbe6af984b28791aca4d2cc0844244e38bce29e918360005260046020526152d28183604060002061585a565b906146cc93506153c2565b906040519182815491828252602082019060005260206000209260005b8181106154aa5750506146cc92500383613d60565b8454835260019485019487945060209093019201615495565b80548210156146115760005260206000200190600090565b600081815260076020526040902054801561566a577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818111613f2057600654907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201918211613f20578181036155fb575b50505060065480156155cc577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff016155898160066154c3565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82549160031b1b19169055600655600052600760205260006040812055600190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b61565261560c61561d9360066154c3565b90549060031b1c92839260066154c3565b81939154907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060031b92831b921b19161790565b90556000526007602052604060002055388080615550565b5050600090565b906001820191816000528260205260406000205480151560001461579c577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818111613f20578254907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201918211613f2057818103615765575b505050805480156155cc577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff019061572682826154c3565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82549160031b1b191690555560005260205260006040812055600190565b61578561577561561d93866154c3565b90549060031b1c928392866154c3565b9055600052836020526040600020553880806156ee565b50505050600090565b806000526007602052604060002054156000146157ff5760065468010000000000000000811015613cdd576157e661561d82600185940160065560066154c3565b9055600654906000526007602052604060002055600190565b50600090565b600082815260018201602052604090205461566a5780549068010000000000000000821015613cdd578261584361561d8460018096018555846154c3565b905580549260005201602052604060002055600190565b9182549060ff8260a01c16158015615b0e575b615b08576fffffffffffffffffffffffffffffffff821691600185019081546158b263ffffffff6fffffffffffffffffffffffffffffffff83169360801c1642613f88565b9081615a6a575b5050848110615a1e57508383106159135750506158e86fffffffffffffffffffffffffffffffff928392613f88565b16167fffffffffffffffffffffffffffffffff00000000000000000000000000000000825416179055565b9190915460801c9283156159b2578161592b91613f88565b927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810190808211613f205761597961597e9273ffffffffffffffffffffffffffffffffffffffff966151a1565b613f4f565b7fd0c8d23a000000000000000000000000000000000000000000000000000000006000526004526024521660445260646000fd5b73ffffffffffffffffffffffffffffffffffffffff83837fd0c8d23a000000000000000000000000000000000000000000000000000000006000527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6004526024521660445260646000fd5b828573ffffffffffffffffffffffffffffffffffffffff927f1a76572a000000000000000000000000000000000000000000000000000000006000526004526024521660445260646000fd5b828692939611615ade57615a8592614bc29160801c90613f0d565b80841015615ad95750825b85547fffffffffffffffffffffffff00000000ffffffffffffffffffffffffffffffff164260801b73ffffffff00000000000000000000000000000000161786559238806158b9565b615a90565b7f9725942a0000000000000000000000000000000000000000000000000000000060005260046000fd5b50505050565b50821561586d56fea164736f6c634300081a000a' as const +// generate:end diff --git a/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/cross-chain-token.ts b/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/cross-chain-token.ts new file mode 100644 index 000000000..90d44e7c4 --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/cross-chain-token.ts @@ -0,0 +1,4 @@ +export default // generate: +// `'${require('fs').readFileSync(require('module').createRequire(`${process.cwd()}/package.json`).resolve('@chainlink/contracts-ccip/bytecode/v2_0_0/cross_chain_token.bin'), 'utf8').trim()}' as const` +'0x60c06040523461072757612e58803803806100198161072c565b92833981016060828203126107275781516001600160401b03811161072757820160e081830312610727576040519160e083016001600160401b0381118482101761061e5760405281516001600160401b038111610727578161007d918401610751565b83526020820151906001600160401b0382116107275761009e918301610751565b9081602084015260408101519060408401918252606081015191606085019283526100cb608083016107bc565b916080860192835260a08101519060ff821682036107275760c06100f69160a08901938452016107bc565b9460c08701958652610116604061010f60208b016107bc565b99016107bc565b6001600160a01b038116610721575033965b518051906001600160401b03821161061e5760035490600182811c92168015610717575b60208310146105fe5781601f8493116106a7575b50602090601f831160011461063f57600092610634575b50508160011b916000199060031b1c1916176003555b8051906001600160401b03821161061e57600454600181811c91168015610614575b60208210146105fe57601f8111610599575b50602090601f831160011461052d5760ff93929160009183610522575b50508160011b916000199060031b1c1916176004555b51166080525160a0528151156104f75780516001600160a01b0316156104e657519051906001600160a01b031680156104d0573081146104bc57600254918083018093116104a6576020926002557fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef600093849284845283825260408420818154019055604051908152a360a05180610480575b50505b516001600160a01b03168061047b5750335b600580546001600160a01b039283166001600160a01b0319821681179092559091167f9524c9e4b0b61eb018dd58a1cd856e3e74009528328ab4a613b434fa631d7242600080a36001600160a01b0381161561046557600780546001600160d01b0316905561030b906107d0565b506001600160a01b038116610455575b7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6600081815260066020527f3195c024b2ddd6d9b8f6c836aa52f67fe69376c8903d009b80229b3ce4425f528054600080516020612e3883398151915291829055909290917fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff9080a47f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a848600081815260066020527f42d20fd6db25ea5a8e33f43724ad72f2ebd9488257fa78c86176b8175fc383fb8054600080516020612e3883398151915291829055909290917fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff9080a460405161257990816108bf823960805181611417015260a051818181610330015261113a0152f35b61045e9061081b565b503861031b565b636116401160e11b600052600060045260246000fd5b61029d565b6002548181116104905750610288565b637502c12360e11b835260045260245260449150fd5b634e487b7160e01b600052601160045260246000fd5b63ec442f0560e01b60005260045260246000fd5b63ec442f0560e01b600052600060045260246000fd5b634dd371db60e11b60005260046000fd5b516001600160a01b031690508061050e575061028b565b63f5c8f5a160e01b60005260045260246000fd5b0151905038806101de565b90601f198316916004600052816000209260005b818110610581575091600193918560ff97969410610568575b505050811b016004556101f4565b015160001960f88460031b161c1916905538808061055a565b92936020600181928786015181550195019301610541565b60046000527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b601f840160051c810191602085106105f4575b601f0160051c01905b8181106105e857506101c1565b600081556001016105db565b90915081906105d2565b634e487b7160e01b600052602260045260246000fd5b90607f16906101af565b634e487b7160e01b600052604160045260246000fd5b015190503880610177565b600360009081528281209350601f198516905b81811061068f5750908460019594939210610676575b505050811b0160035561018d565b015160001960f88460031b161c19169055388080610668565b92936020600181928786015181550195019301610652565b60036000529091507fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b601f840160051c8101916020851061070d575b90601f859493920160051c01905b8181106106fe5750610160565b600081558493506001016106f1565b90915081906106e3565b91607f169161014c565b96610128565b600080fd5b6040519190601f01601f191682016001600160401b0381118382101761061e57604052565b81601f82011215610727578051906001600160401b03821161061e57610780601f8301601f191660200161072c565b92828452602083830101116107275760005b8281106107a757505060206000918301015290565b80602080928401015182828701015201610792565b51906001600160a01b038216820361072757565b600854906001600160a01b03821661080a576001600160a01b03199091166001600160a01b0382161760085561080790600061082f565b90565b631fe1e13d60e11b60005260046000fd5b61080790600080516020612e388339815191525b60008181526006602090815260408083206001600160a01b038616845290915290205460ff166108b75760008181526006602090815260408083206001600160a01b0395909516808452949091528120805460ff19166001179055339291907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d9080a4600190565b505060009056fe608080604052600436101561001357600080fd5b60003560e01c90816301ffc9a7146119d457508063022d63fb1461199857806306fdde03146118bb578063095ea7b3146117795780630aa6220b1461169357806318160ddd14611657578063181f5a77146115a157806323b872dd1461154b578063248a9ca3146114f8578063282c51f31461149f5780632f2ff15d1461143b578063313ce567146113df57806336568abe1461125057806340c10f191461105157806342966c681461100e578063634e93da14610eb7578063649a5ec714610c8757806370a0823114610c2257806379cc67901461095657806384ef8ffc14610bd05780638da5cb5b14610bd05780638fd6a6ac14610b7e57806391d1485414610b0557806395d89b41146109ac5780639dc29fac14610956578063a1eda53c146108d1578063a217fddf14610897578063a8fa343c146107ec578063a9059cbb1461079d578063c630948d146106ac578063c91ddc2014610653578063cc8463c81461060a578063cefc1429146104cc578063cf6eefb714610441578063d5391393146103e8578063d547741f14610353578063d5abeb01146102fa578063d602b9fd146102615763dd62ed3e146101cc57600080fd5b3461025c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c57610203611c1c565b73ffffffffffffffffffffffffffffffffffffffff610220611c3f565b9116600052600160205273ffffffffffffffffffffffffffffffffffffffff604060002091166000526020526020604060002054604051908152f35b600080fd5b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c57610298611cdc565b600780547fffffffffffff0000000000000000000000000000000000000000000000000000811690915560a01c65ffffffffffff166102d357005b7f8886ebfc4259abdbc16601dd8fb5678e54878f47b3c34836cfc51154a9605109600080a1005b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b3461025c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c5760043561038d611c3f565b81156103be57816103b76103b26103bc94600052600660205260016040600020015490565b611dd3565b6122f9565b005b7f3fc3c27a0000000000000000000000000000000000000000000000000000000060005260046000fd5b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c5760206040517f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a68152f35b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c57604065ffffffffffff6104a66007549065ffffffffffff73ffffffffffffffffffffffffffffffffffffffff83169260a01c1690565b73ffffffffffffffffffffffffffffffffffffffff849392935193168352166020820152f35b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c5760075473ffffffffffffffffffffffffffffffffffffffff1633036105dc5760075460a081901c65ffffffffffff169073ffffffffffffffffffffffffffffffffffffffff16811580156105d2575b6105a4576105799061057373ffffffffffffffffffffffffffffffffffffffff6008541661228b565b506121af565b50600780547fffffffffffff0000000000000000000000000000000000000000000000000000169055005b507f19ca5ebb0000000000000000000000000000000000000000000000000000000060005260045260246000fd5b504282101561054a565b7fc22c8022000000000000000000000000000000000000000000000000000000006000523360045260246000fd5b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c576020610643611ca3565b65ffffffffffff60405191168152f35b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c5760206040517fcfd2b420c3d2b6ebd6af82f6e29c095b45a072b8d1b5d9eda2a56dcb850acaa68152f35b3461025c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c576103bc6106e6611c1c565b7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a660005260066020527f3195c024b2ddd6d9b8f6c836aa52f67fe69376c8903d009b80229b3ce4425f525461073a90611dd3565b61074381612158565b507f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a84860005260066020527f42d20fd6db25ea5a8e33f43724ad72f2ebd9488257fa78c86176b8175fc383fb5461079890611dd3565b612185565b3461025c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c576107e16107d7611c1c565b6024359033611f5d565b602060405160018152f35b3461025c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c57610823611c1c565b61082b611cdc565b73ffffffffffffffffffffffffffffffffffffffff80600554921691827fffffffffffffffffffffffff0000000000000000000000000000000000000000821617600555167f9524c9e4b0b61eb018dd58a1cd856e3e74009528328ab4a613b434fa631d7242600080a3005b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c57602060405160008152f35b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c576008548060d01c908115158061094c575b156109425760a01c65ffffffffffff165b6040805165ffffffffffff928316815292909116602083015290f35b0390f35b5050600080610922565b5042821015610911565b3461025c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c576103bc610990611c1c565b6024359061099c611d48565b6109a7823383611e40565b61208d565b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c5760405160006004548060011c90600181168015610afb575b602083108114610ace57828552908115610a8c5750600114610a2c575b61093e83610a2081850382611c62565b60405191829182611bb4565b91905060046000527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b916000905b808210610a7257509091508101602001610a20610a10565b919260018160209254838588010152019101909291610a5a565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660208086019190915291151560051b84019091019150610a209050610a10565b6024847f4e487b710000000000000000000000000000000000000000000000000000000081526022600452fd5b91607f16916109f3565b3461025c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c57610b3c611c3f565b600435600052600660205273ffffffffffffffffffffffffffffffffffffffff60406000209116600052602052602060ff604060002054166040519015158152f35b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c57602073ffffffffffffffffffffffffffffffffffffffff60055416604051908152f35b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c57602073ffffffffffffffffffffffffffffffffffffffff60085416604051908152f35b3461025c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c5773ffffffffffffffffffffffffffffffffffffffff610c6e611c1c565b1660005260006020526020604060002054604051908152f35b3461025c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c5760043565ffffffffffff81169081810361025c57610cd2611cdc565b610cdb4261236f565b9165ffffffffffff610ceb611ca3565b1680821115610e4e57507ff1038c18cf84a56e432fdbfaf746924b7ea511dfe03a6506a0ceba4888788d9b9265ffffffffffff826206978080610d3895109118026206978018169061213a565b906008548060d01c80610dca575b50506008805473ffffffffffffffffffffffffffffffffffffffff1660a083901b79ffffffffffff0000000000000000000000000000000000000000161760d084901b7fffffffffffff0000000000000000000000000000000000000000000000000000161790556040805165ffffffffffff9283168152919092166020820152a1005b421115610e235779ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff00000000000000000000000000000000000000000000000000006007549260301b169116176007555b8380610d46565b507f2b1fa2edafe6f7b9e97c1a9e0c3660e645beb2dcaa2d45bdbf9beaf5472e1ec5600080a1610e1c565b0365ffffffffffff8111610e88577ff1038c18cf84a56e432fdbfaf746924b7ea511dfe03a6506a0ceba4888788d9b92610d38919061213a565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b3461025c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c57610eee611c1c565b610ef6611cdc565b7f3377dc44241e779dd06afab5b788a35ca5f3b778836e2990bdb26a2a4b2e5ed66020610f33610f254261236f565b610f2d611ca3565b9061213a565b65ffffffffffff73ffffffffffffffffffffffffffffffffffffffff610f7c6007549065ffffffffffff73ffffffffffffffffffffffffffffffffffffffff83169260a01c1690565b9690501694600754867fffffffffffff000000000000000000000000000000000000000000000000000079ffffffffffff00000000000000000000000000000000000000008660a01b169216171760075516610fe4575b65ffffffffffff60405191168152a2005b7f8886ebfc4259abdbc16601dd8fb5678e54878f47b3c34836cfc51154a9605109600080a1610fd3565b3461025c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c57611045611d48565b6103bc6004353361208d565b3461025c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c57611088611c1c565b3360009081527f3195c024b2ddd6d9b8f6c836aa52f67fe69376c8903d009b80229b3ce4425f516020526040902054602435919060ff16156111fe5773ffffffffffffffffffffffffffffffffffffffff1680156111cf573081146111a25760025491808301809311610e88576020926002557fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef600093849284845283825260408420818154019055604051908152a37f000000000000000000000000000000000000000000000000000000000000000080611162575080f35b90600254918083116111745750905080f35b6044927fea058246000000000000000000000000000000000000000000000000000000008352600452602452fd5b7fec442f050000000000000000000000000000000000000000000000000000000060005260045260246000fd5b7fec442f0500000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b7fe2517d3f00000000000000000000000000000000000000000000000000000000600052336004527f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a660245260446000fd5b3461025c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c5760043561128a611c3f565b8115806113a8575b6112e7575b3373ffffffffffffffffffffffffffffffffffffffff8216036112bd576103bc916122f9565b7f6697b2320000000000000000000000000000000000000000000000000000000060005260046000fd5b60075465ffffffffffff60a082901c169073ffffffffffffffffffffffffffffffffffffffff1615801590611398575b8015611386575b61135057507fffffffffffff000000000000ffffffffffffffffffffffffffffffffffffffff60075416600755611297565b65ffffffffffff907f19ca5ebb000000000000000000000000000000000000000000000000000000006000521660045260246000fd5b504265ffffffffffff8216101561131e565b5065ffffffffffff811615611317565b5073ffffffffffffffffffffffffffffffffffffffff6008541673ffffffffffffffffffffffffffffffffffffffff821614611292565b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c57602060405160ff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b3461025c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c57600435611475611c3f565b81156103be578161149a6103b26103bc94600052600660205260016040600020015490565b612217565b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c5760206040517f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a8488152f35b3461025c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c576020611543600435600052600660205260016040600020015490565b604051908152f35b3461025c5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c576107e1611585611c1c565b61158d611c3f565b6044359161159c833383611e40565b611f5d565b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c57604051604081019080821067ffffffffffffffff8311176116285761093e91604052601581527f43726f7373436861696e546f6b656e20322e302e300000000000000000000000602082015260405191829182611bb4565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c576020600254604051908152f35b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c576116ca611cdc565b6008548060d01c806116f5575b6008805473ffffffffffffffffffffffffffffffffffffffff169055005b42111561174e5779ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff00000000000000000000000000000000000000000000000000006007549260301b169116176007555b80806116d7565b507f2b1fa2edafe6f7b9e97c1a9e0c3660e645beb2dcaa2d45bdbf9beaf5472e1ec5600080a1611747565b3461025c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c576117b0611c1c565b73ffffffffffffffffffffffffffffffffffffffff1660243530821461188d57331561185e57811561182f57336000526001602052604060002082600052602052806040600020556040519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560203392a3602060405160018152f35b7f94280d6200000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b7fe602df0500000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b507f94280d620000000000000000000000000000000000000000000000000000000060005260045260246000fd5b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c5760405160006003548060011c9060018116801561198e575b602083108114610ace57828552908115610a8c575060011461192e5761093e83610a2081850382611c62565b91905060036000527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b916000905b80821061197457509091508101602001610a20610a10565b91926001816020925483858801015201910190929161195c565b91607f1691611902565b3461025c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c576020604051620697808152f35b3461025c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261025c57600435907fffffffff00000000000000000000000000000000000000000000000000000000821680920361025c57817f314987860000000000000000000000000000000000000000000000000000000060209314908115611b59575b8115611a9e575b8115611a74575b5015158152f35b7fe6599b4d0000000000000000000000000000000000000000000000000000000091501483611a6d565b90507f36372b070000000000000000000000000000000000000000000000000000000081148015611b30575b8015611b07575b8015611ade575b90611a66565b507f01ffc9a7000000000000000000000000000000000000000000000000000000008114611ad8565b507fa219a025000000000000000000000000000000000000000000000000000000008114611ad1565b507f8fd6a6ac000000000000000000000000000000000000000000000000000000008114611aca565b90507f7965db0b0000000000000000000000000000000000000000000000000000000081148015611b8b575b90611a5f565b507f01ffc9a7000000000000000000000000000000000000000000000000000000008114611b85565b9190916020815282519283602083015260005b848110611c065750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006040809697860101520116010190565b8060208092840101516040828601015201611bc7565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361025c57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361025c57565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761162857604052565b6008548060d01c8015159081611cd2575b5015611cc85760a01c65ffffffffffff1690565b5060075460d01c90565b9050421138611cb4565b3360009081527f54cdd369e4e8a8515e52ca72ec816c2101831ad1f18bf44102ed171459c9b4f8602052604090205460ff1615611d1557565b7fe2517d3f0000000000000000000000000000000000000000000000000000000060005233600452600060245260446000fd5b3360009081527f42d20fd6db25ea5a8e33f43724ad72f2ebd9488257fa78c86176b8175fc383fa602052604090205460ff1615611d8157565b7fe2517d3f00000000000000000000000000000000000000000000000000000000600052336004527f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a84860245260446000fd5b806000526006602052604060002073ffffffffffffffffffffffffffffffffffffffff331660005260205260ff6040600020541615611e0f5750565b7fe2517d3f000000000000000000000000000000000000000000000000000000006000523360045260245260446000fd5b73ffffffffffffffffffffffffffffffffffffffff9092919216806000526001602052604060002073ffffffffffffffffffffffffffffffffffffffff8416600052602052604060002054927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8410611eba575b50505050565b828410611f115773ffffffffffffffffffffffffffffffffffffffff169030821461188d57801561185e57811561182f57600052600160205260406000209060005260205260406000209103905538808080611eb4565b8373ffffffffffffffffffffffffffffffffffffffff84927ffb8f41b2000000000000000000000000000000000000000000000000000000006000521660045260245260445260646000fd5b73ffffffffffffffffffffffffffffffffffffffff1690811561205e5773ffffffffffffffffffffffffffffffffffffffff169182156111cf57308314612030576000828152806020526040812054828110611ffd5791604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815280845220818154019055604051908152a3565b6064937fe450d38c0000000000000000000000000000000000000000000000000000000083949352600452602452604452fd5b827fec442f050000000000000000000000000000000000000000000000000000000060005260045260246000fd5b7f96c6fd1e00000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b73ffffffffffffffffffffffffffffffffffffffff16801561205e5730156111cf5760009181835282602052604083205481811061210857817fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef926020928587528684520360408620558060025403600255604051908152a3565b83927fe450d38c0000000000000000000000000000000000000000000000000000000060649552600452602452604452fd5b9065ffffffffffff8091169116019065ffffffffffff8211610e8857565b612182907f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a66123b9565b90565b612182907f3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a8486123b9565b6008549073ffffffffffffffffffffffffffffffffffffffff82166103be57612182917fffffffffffffffffffffffff000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff831691161760085560006123b9565b908115612228575b612182916123b9565b6008549173ffffffffffffffffffffffffffffffffffffffff83166103be577fffffffffffffffffffffffff000000000000000000000000000000000000000090921673ffffffffffffffffffffffffffffffffffffffff82161760085561221f565b6121829073ffffffffffffffffffffffffffffffffffffffff6008541673ffffffffffffffffffffffffffffffffffffffff8216146122cc575b6000612498565b7fffffffffffffffffffffffff0000000000000000000000000000000000000000600854166008556122c5565b9061218291801580612338575b15612498577fffffffffffffffffffffffff000000000000000000000000000000000000000060085416600855612498565b5073ffffffffffffffffffffffffffffffffffffffff6008541673ffffffffffffffffffffffffffffffffffffffff831614612306565b65ffffffffffff81116123875765ffffffffffff1690565b7f6dfcc65000000000000000000000000000000000000000000000000000000000600052603060045260245260446000fd5b806000526006602052604060002073ffffffffffffffffffffffffffffffffffffffff831660005260205260ff604060002054161560001461249157806000526006602052604060002073ffffffffffffffffffffffffffffffffffffffff8316600052602052604060002060017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082541617905573ffffffffffffffffffffffffffffffffffffffff339216907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d600080a4600190565b5050600090565b806000526006602052604060002073ffffffffffffffffffffffffffffffffffffffff831660005260205260ff6040600020541660001461249157806000526006602052604060002073ffffffffffffffffffffffffffffffffffffffff831660005260205260406000207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00815416905573ffffffffffffffffffffffffffffffffffffffff339216907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b600080a460019056fea164736f6c634300081a000acfd2b420c3d2b6ebd6af82f6e29c095b45a072b8d1b5d9eda2a56dcb850acaa6' as const +// generate:end diff --git a/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/erc20-lockbox.ts b/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/erc20-lockbox.ts new file mode 100644 index 000000000..898d3cbd1 --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/erc20-lockbox.ts @@ -0,0 +1,4 @@ +export default // generate: +// `'${require('fs').readFileSync(require('module').createRequire(`${process.cwd()}/package.json`).resolve('@chainlink/contracts-ccip/bytecode/v2_0_0/erc20_lock_box.bin'), 'utf8').trim()}' as const` +'0x60a0604052346101d9576113cf6020813803918261001c816101de565b9384928339810103126101d957516001600160a01b038116908190036101d957602090610048826101de565b9160008352600036813733156101c857600180546001600160a01b03191633179055610073816101de565b60008152600036813760408051949085016001600160401b038111868210176101b2576040528452808285015260005b815181101561010a576001906001600160a01b036100c18285610203565b5116846100cd82610245565b6100da575b5050016100a3565b7fc3803387881faad271c47728894e3e36fac830ffc8602ca6fc07733cbda7758091604051908152a138846100d2565b5050915160005b8151811015610182576001600160a01b0361012c8284610203565b5116908115610171577feb1b9b92e50b7f88f9ff25d56765095ac6e91540eee214906f4036a908ffbdef8583610163600195610343565b50604051908152a101610111565b6342bcdf7f60e11b60005260046000fd5b8280156101715760805260405161102b90816103a482396080518181816105f6015281816109960152610c060152f35b634e487b7160e01b600052604160045260246000fd5b639b15e16f60e01b60005260046000fd5b600080fd5b6040519190601f01601f191682016001600160401b038111838210176101b257604052565b80518210156102175760209160051b010190565b634e487b7160e01b600052603260045260246000fd5b80548210156102175760005260206000200190600090565b600081815260036020526040902054801561033c57600019810181811161032657600254600019810191908211610326578082036102d5575b50505060025480156102bf576000190161029981600261022d565b8154906000199060031b1b19169055600255600052600360205260006040812055600190565b634e487b7160e01b600052603160045260246000fd5b61030e6102e66102f793600261022d565b90549060031b1c928392600261022d565b819391549060031b91821b91600019901b19161790565b9055600052600360205260406000205538808061027e565b634e487b7160e01b600052601160045260246000fd5b5050600090565b8060005260036020526040600020541560001461039d57600254680100000000000000008110156101b2576103846102f7826001859401600255600261022d565b9055600254906000526003602052604060002055600190565b5060009056fe608080604052600436101561001357600080fd5b60003560e01c908163181f5a77146109ba5750806321df0da71461094b5780632451a6271461085d57806374fd18ac1461061b57806375151b631461058c57806379ba5097146104a35780638da5cb5b1461045157806391a2749a14610267578063a36a7fee146101825763f2fde38b1461008d57600080fd5b3461017d5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261017d5773ffffffffffffffffffffffffffffffffffffffff6100d9610a89565b6100e1610cc8565b1633811461015357807fffffffffffffffffffffffff0000000000000000000000000000000000000000600054161760005573ffffffffffffffffffffffffffffffffffffffff600154167fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278600080a3005b7fdad89dca0000000000000000000000000000000000000000000000000000000060005260046000fd5b600080fd5b3461017d5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261017d576101b9610a89565b6101c1610aac565b5073ffffffffffffffffffffffffffffffffffffffff604435916101e58382610bd3565b166102396040517f23b872dd00000000000000000000000000000000000000000000000000000000602082015233602482015230604482015283606482015260648152610233608482610b0e565b82610d56565b6040519182527f5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f6260203393a3005b3461017d5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261017d5760043567ffffffffffffffff811161017d5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc823603011261017d57604051906102e182610ac3565b806004013567ffffffffffffffff811161017d576103059060043691840101610b4f565b825260248101359067ffffffffffffffff821161017d57600461032b9236920101610b4f565b6020820190815261033a610cc8565b519060005b82518110156103b2578073ffffffffffffffffffffffffffffffffffffffff61036a60019386610d13565b511661037581610df9565b610381575b500161033f565b60207fc3803387881faad271c47728894e3e36fac830ffc8602ca6fc07733cbda7758091604051908152a18461037a565b505160005b815181101561044f5773ffffffffffffffffffffffffffffffffffffffff6103df8284610d13565b5116908115610425577feb1b9b92e50b7f88f9ff25d56765095ac6e91540eee214906f4036a908ffbdef602083610417600195610fbe565b50604051908152a1016103b7565b7f8579befe0000000000000000000000000000000000000000000000000000000060005260046000fd5b005b3461017d5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261017d57602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b3461017d5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261017d5760005473ffffffffffffffffffffffffffffffffffffffff81163303610562577fffffffffffffffffffffffff00000000000000000000000000000000000000006001549133828416176001551660005573ffffffffffffffffffffffffffffffffffffffff3391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3005b7f02b543c60000000000000000000000000000000000000000000000000000000060005260046000fd5b3461017d5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261017d5760206105c5610a89565b73ffffffffffffffffffffffffffffffffffffffff604051911673ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016148152f35b3461017d5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261017d57610652610a89565b61065a610aac565b506044356064359173ffffffffffffffffffffffffffffffffffffffff831680930361017d57819061068c8382610bd3565b83156108335773ffffffffffffffffffffffffffffffffffffffff1691604051907f70a08231000000000000000000000000000000000000000000000000000000008252306004830152602082602481875afa918215610827576000926107d0575b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146107c8575b808211610797575060207f2717ead6b9200dd235aad468c9809ea400fe33ac69b5bfaa6d3e90fc922b63989161078e6040517fa9059cbb000000000000000000000000000000000000000000000000000000008482015286602482015282604482015260448152610788606482610b0e565b85610d56565b604051908152a3005b907fcf4791810000000000000000000000000000000000000000000000000000000060005260045260245260446000fd5b905080610716565b90916020823d60201161081f575b816107eb60209383610b0e565b8101031261081c575051907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6106ee565b80fd5b3d91506107de565b6040513d6000823e3d90fd5b7fd87070520000000000000000000000000000000000000000000000000000000060005260046000fd5b3461017d5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261017d576040518060206002549283815201809260026000527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace9060005b81811061093557505050816108dc910382610b0e565b6040519182916020830190602084525180915260408301919060005b818110610906575050500390f35b825173ffffffffffffffffffffffffffffffffffffffff168452859450602093840193909201916001016108f8565b82548452602090930192600192830192016108c6565b3461017d5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261017d57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b3461017d5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261017d576109f281610ac3565b601281527f45524332304c6f636b426f7820322e302e300000000000000000000000000000602082015260405190602082528181519182602083015260005b838110610a715750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f836000604080968601015201168101030190f35b60208282018101516040878401015285935001610a31565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361017d57565b6024359067ffffffffffffffff8216820361017d57565b6040810190811067ffffffffffffffff821117610adf57604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610adf57604052565b81601f8201121561017d5780359167ffffffffffffffff8311610adf578260051b9160405193610b826020850186610b0e565b845260208085019382010191821161017d57602001915b818310610ba65750505090565b823573ffffffffffffffffffffffffffffffffffffffff8116810361017d57815260209283019201610b99565b9015610c9e5773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168103610c71575033600052600360205260406000205415610c4357565b7fd86ad9cf000000000000000000000000000000000000000000000000000000006000523360045260246000fd5b7fbf16aab60000000000000000000000000000000000000000000000000000000060005260045260246000fd5b7f8b1fa9dd0000000000000000000000000000000000000000000000000000000060005260046000fd5b73ffffffffffffffffffffffffffffffffffffffff600154163303610ce957565b7f2b5c74de0000000000000000000000000000000000000000000000000000000060005260046000fd5b8051821015610d275760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b906000602091828151910182855af115610827576000513d610dd8575073ffffffffffffffffffffffffffffffffffffffff81163b155b610d945750565b73ffffffffffffffffffffffffffffffffffffffff907f5274afe7000000000000000000000000000000000000000000000000000000006000521660045260246000fd5b60011415610d8d565b8054821015610d275760005260206000200190600090565b6000818152600360205260409020548015610fb7577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818111610f8857600254907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201918211610f8857808203610f19575b5050506002548015610eea577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01610ea7816002610de1565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82549160031b1b19169055600255600052600360205260006040812055600190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b610f70610f2a610f3b936002610de1565b90549060031b1c9283926002610de1565b81939154907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060031b92831b921b19161790565b90556000526003602052604060002055388080610e6e565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5050600090565b806000526003602052604060002054156000146110185760025468010000000000000000811015610adf57610fff610f3b8260018594016002556002610de1565b9055600254906000526003602052604060002055600190565b5060009056fea164736f6c634300081a000a' as const +// generate:end diff --git a/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/lock-release-token-pool.ts b/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/lock-release-token-pool.ts new file mode 100644 index 000000000..aca19d5f1 --- /dev/null +++ b/ccip-sdk/src/cct/evm/artifacts/bytecode/V2_0_0/lock-release-token-pool.ts @@ -0,0 +1,4 @@ +export default // generate: +// `'${require('fs').readFileSync(require('module').createRequire(`${process.cwd()}/package.json`).resolve('@chainlink/contracts-ccip/bytecode/v2_0_0/lock_release_token_pool.bin'), 'utf8').trim()}' as const` +'0x610100806040523461037a5760c081616038803803809161002082856103ba565b83398101031261037a5780516001600160a01b0381169182820361037a5761004a602082016103f3565b9061005760408201610401565b61006360608301610401565b9261007c60a061007560808601610401565b9401610401565b9333156103a957600180546001600160a01b0319163317905586158015610398575b8015610387575b6102df578560805260c0523086036102f0575b60a052600380546001600160a01b03199081166001600160a01b03938416179091556002805490911692821692909217909155169182156102df576040516375151b6360e01b815260048101829052602081602481875afa9081156102d357600091610291575b501561027d57604051906020600081840163095ea7b360e01b815286602486015281196044860152604485526101566064866103ba565b84519082875af1903d600051908361025e575b50505015610219575b8260e052604051615bc79081610471823960805181818161024a015281816121d3015281816129c701528181612c3a015281816130e8015281816137140152818161376e0152614f0c015260a0518181816135da015281816148ed015281816149370152614f60015260c0518181816102e50152818161134d0152818161226d01528181612a620152613183015260e0518181816126cf01528181612bc10152614e910152f35b6102579161025260405163095ea7b360e01b6020820152856024820152600060448201526044815261024c6064826103ba565b82610415565b610415565b3880610172565b9192509061027357503b15155b388080610169565b600191501461026b565b63961c9a4f60e01b60005260045260246000fd5b6020813d6020116102cb575b816102aa602093836103ba565b810103126102c757519081151582036102c457503861011f565b80fd5b5080fd5b3d915061029d565b6040513d6000823e3d90fd5b630a64406560e11b60005260046000fd5b60405163313ce56760e01b81526020816004818a5afa60009181610346575b5061031b575b506100b8565b60ff1660ff821681810361032f5750610315565b6332ad3e0760e11b60005260045260245260446000fd5b9091506020813d60201161037f575b81610362602093836103ba565b8101031261037a57610373906103f3565b903861030f565b600080fd5b3d9150610355565b506001600160a01b038116156100a5565b506001600160a01b0384161561009e565b639b15e16f60e01b60005260046000fd5b601f909101601f19168101906001600160401b038211908210176103dd57604052565b634e487b7160e01b600052604160045260246000fd5b519060ff8216820361037a57565b51906001600160a01b038216820361037a57565b906000602091828151910182855af1156102d3576000513d61046757506001600160a01b0381163b155b6104465750565b635274afe760e01b60009081526001600160a01b0391909116600452602490fd5b6001141561043f56fe608080604052600436101561001357600080fd5b600090813560e01c90816301ffc9a71461398f5750806306b859ef146138aa578063181f5a77146138495780631826b1e71461379257806321df0da714613741578063240028e8146136dd5780632422ac45146135fe57806324f65ee7146135c05780632cab0fb61461304d57806337a3210d14613019578063390775371461291c5780634c5ef0ed146128d557806362ddd3c41461284e5780637437ff9f1461280057806379ba5097146127395780638926f54f146126f35780638c6894fb146126a25780638da5cb5b1461266e5780639a4575b91461215a578063a42a7b8b14611ff3578063acfecf9114611efb578063ae39a25714611d70578063b6cfa3b714611cb5578063b794658014611c7d578063bfeffd3f14611bd1578063c4bffe2b14611aa6578063c7230a60146117f5578063dc04fa1f14611371578063dc0bd97114611320578063dcbd41bc1461111c578063e8a1da1714610a44578063ea6396db14610906578063ec6ae7a7146108c3578063f2fde38b146107f45763fbc801a7146101a257600080fd5b34610668576060600319360112610668576004359067ffffffffffffffff8211610668578160040160a060031984360301126107f0576101e0613ac1565b9160443567ffffffffffffffff81116107f0579061020661022393923690600401613bec565b93906102106145a9565b5061021b86856151c4565b943691613d66565b93608486019461023286614536565b73ffffffffffffffffffffffffffffffffffffffff807f0000000000000000000000000000000000000000000000000000000000000000169116036107a657602487019677ffffffffffffffff0000000000000000000000000000000061029889614557565b60801b16604051907f2cbc26bb000000000000000000000000000000000000000000000000000000008252600482015260208160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa908115610719578591610777575b5061074f5767ffffffffffffffff61032c89614557565b16610344816000526007602052604060002054151590565b1561072457602073ffffffffffffffffffffffffffffffffffffffff60025416916024604051809481937fa8d87a3b00000000000000000000000000000000000000000000000000000000835260048301525afa80156107195785906106c8575b73ffffffffffffffffffffffffffffffffffffffff915016330361069c576064810135946103d38787613f4d565b7fffffffff00000000000000000000000000000000000000000000000000000000851694851561067a5761042f907fffffffff0000000000000000000000000000000000000000000000000000000060025460401b1690614a41565b61044b8161043c8b614536565b6104458d614557565b906154ac565b73ffffffffffffffffffffffffffffffffffffffff600354169384610549575b61053f8a61050e6105098e6104808e8e613f4d565b936104938561048e84614557565b614e7a565b7ff33bc26b4413b0e7f19f1ea739fdf99098c0061f1f87d954b11f5293fad9ae1067ffffffffffffffff6104cf6104c985614557565b93614536565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252336020830152810188905292169180606081015b0390a2614557565b61471a565b90610517614f59565b6040519261052484613cd1565b83526020830152604051928392604084526040840190613e2e565b9060208301520390f35b843b15610676578694928a949286928d604051998a98899788967fa8027c0f00000000000000000000000000000000000000000000000000000000885260048801608090528061059891615416565b6084890160a090526101248901906105af92613f7b565b936105b990613bd7565b67ffffffffffffffff1660a48801526044016105d490613b88565b73ffffffffffffffffffffffffffffffffffffffff1660c48701528d60e48701526105fe90613b88565b73ffffffffffffffffffffffffffffffffffffffff16610104860152602485015283810360031901604485015261063491613c1a565b90606483015203925af1801561066b57610653575b808080808061046b565b61065e828092613d25565b6106685780610649565b80fd5b6040513d84823e3d90fd5b8680fd5b50610697816106888b614536565b6106918d614557565b90615466565b61044b565b6024847f728fe07b00000000000000000000000000000000000000000000000000000000815233600452fd5b506020813d602011610711575b816106e260209383613d25565b8101031261070d5761070873ffffffffffffffffffffffffffffffffffffffff91613f5a565b6103a5565b8480fd5b3d91506106d5565b6040513d87823e3d90fd5b7fa9902c7e000000000000000000000000000000000000000000000000000000008552600452602484fd5b6004847f53ad11d8000000000000000000000000000000000000000000000000000000008152fd5b610799915060203d60201161079f575b6107918183613d25565b810190614bad565b38610315565b503d610787565b60248373ffffffffffffffffffffffffffffffffffffffff6107c789614536565b7f961c9a4f00000000000000000000000000000000000000000000000000000000835216600452fd5b5080fd5b50346106685760206003193601126106685773ffffffffffffffffffffffffffffffffffffffff610823613b1f565b61082b614bc5565b1633811461089b57807fffffffffffffffffffffffff000000000000000000000000000000000000000083541617825573ffffffffffffffffffffffffffffffffffffffff600154167fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12788380a380f35b6004827fdad89dca000000000000000000000000000000000000000000000000000000008152fd5b503461066857806003193601126106685760207fffffffff0000000000000000000000000000000000000000000000000000000060025460401b16604051908152f35b503461066857608060031936011261066857610920613b1f565b50610929613ba9565b610931613af0565b5060643567ffffffffffffffff8111610a40579167ffffffffffffffff60409261096160e0953690600401613bec565b50508260c0855161097181613d09565b82815282602082015282878201528260608201528260808201528260a08201520152168152600b60205220604051906109a982613d09565b5461ffff818163ffffffff82169485815263ffffffff60208201818560201c1681528160408401818760401c168152816060860193818960601c16855260ff60c060808901988a8c60801c168a528a60a082019c60901c168c52019b60a01c1615158b526040519b8c52511660208b0152511660408901525116606087015251166080850152511660a083015251151560c0820152f35b8280fd5b50346106685760406003193601126106685760043567ffffffffffffffff81116107f057610a76903690600401613e58565b9060243567ffffffffffffffff81116111185790610a9984923690600401613e58565b939091610aa4614bc5565b83905b828210610f595750505081927ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee182360301935b81811015610f55578060051b8301358581121561070d5783016101208136031261070d5760405194610b0b86613ced565b610b1482613bd7565b8652602082013567ffffffffffffffff81116107f05782019436601f870112156107f057853595610b4487613eba565b96610b526040519889613d25565b80885260208089019160051b8301019036821161070d5760208301905b828210610f26575050505060208701958652604083013567ffffffffffffffff8111610a4057610ba29036908501613dcb565b9160408801928352610bcc610bba36606087016147c6565b9460608a0195865260c03691016147c6565b956080890196875283515115610efe57610bf067ffffffffffffffff8a5116615849565b15610ec75767ffffffffffffffff8951168252600860205260408220610c17865182614f94565b610c25885160028301614f94565b6004855191019080519067ffffffffffffffff8211610e9a57610c488354614605565b601f8111610e5f575b50602090601f8311600114610dc057610c9f9291869183610db5575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b90555b815b88518051821015610cd95790610cd3600192610ccc8367ffffffffffffffff8f5116926145c2565b5190614c10565b01610ca4565b5050977f8d340f17e19058004c20453540862a9c62778504476f6756755cb33bcd6c38c2939199975095610da767ffffffffffffffff6001979694985116925193519151610d73610d3e60405196879687526101006020880152610100870190613c1a565b9360408601906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565b60a08401906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565b0390a1019392909193610ada565b015190508e80610c6d565b83865281862091907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08416875b818110610e475750908460019594939210610e10575b505050811b019055610ca2565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558d8080610e03565b92936020600181928786015181550195019301610ded565b610e8a9084875260208720601f850160051c81019160208610610e90575b601f0160051c0190614862565b8d610c51565b9091508190610e7d565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b60248267ffffffffffffffff8b51167f1d5ad3c5000000000000000000000000000000000000000000000000000000008252600452fd5b6004827f14c880ca000000000000000000000000000000000000000000000000000000008152fd5b813567ffffffffffffffff811161067657602091610f4a8392833691890101613dcb565b815201910190610b6f565b8380f35b9267ffffffffffffffff610f7b610f768486889a9699979a614799565b614557565b1691610f868361557f565b156110ec578284526008602052610fa26005604086200161551c565b94845b8651811015610fdb576001908587526008602052610fd460056040892001610fcd838b6145c2565b5190615715565b5001610fa5565b50939692909450949094808752600860205260056040882088815588600182015588600282015588600382015588600482016110178154614605565b806110ab575b505050018054908881558161108d575b5050907f5204aec90a3c794d8e90fded8b46ae9c7c552803e7e832e0c1d358396d8599166020836001948a52600482528985604082208281550155808a52600582528985604082208281550155604051908152a101909194939294610aa7565b885260208820908101905b8181101561102d57888155600101611098565b601f81116001146110c15750555b888a8061101d565b818352602083206110dc91601f01861c810190600101614862565b80825281602081209155556110b9565b602484847f1e670e4b000000000000000000000000000000000000000000000000000000008252600452fd5b8380fd5b50346106685760206003193601126106685760043567ffffffffffffffff81116107f05761114e903690600401613e89565b73ffffffffffffffffffffffffffffffffffffffff600a5416331415806112fe575b6112d257825b818110611181578380f35b61118c81838561473c565b67ffffffffffffffff61119e82614557565b16906111b7826000526007602052604060002054151590565b156112a657907f41f7c8f7cfdad9350aa495e6c54cbbf750a07ab38a9098aed1256e30dd1682bb60e083611266611240602060019897018b6111f88261474c565b1561126d57879052600460205261121f60408d2061121936604088016147c6565b90614f94565b868c52600560205261123b60408d206112193660a088016147c6565b61474c565b916040519215158352611259602084016040830161481e565b60a060808401910161481e565ba201611176565b60026040828a61123b9452600860205261128f82822061121936858c016147c6565b8a8152600860205220016112193660a088016147c6565b602486837f1e670e4b000000000000000000000000000000000000000000000000000000008252600452fd5b6024837f8e4a23d600000000000000000000000000000000000000000000000000000000815233600452fd5b5073ffffffffffffffffffffffffffffffffffffffff60015416331415611170565b5034610668578060031936011261066857602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50346106685760406003193601126106685760043567ffffffffffffffff81116107f0576113a3903690600401613e89565b60243567ffffffffffffffff8111611118576113c3903690600401613e58565b9190926113ce614bc5565b845b82811061143a57505050825b8181106113e7578380f35b8067ffffffffffffffff611401610f766001948688614799565b16808652600b6020528560408120557f5479bbc0288b7eaeaf2ace0943b88016cc648964fcd42919a86fd93b15fdbee88680a2016113dc565b67ffffffffffffffff611451610f7683868661473c565b16611469816000526007602052604060002054151590565b156117ca5761147982858561473c565b602081019060e081019061148c8261474c565b1561179e5760a0810161271061ffff6114a483614759565b16101561178f5760c082019161271061ffff6114bf85614759565b1610156117575763ffffffff6114d486614768565b161561172b57858c52600b60205260408c206114ef86614768565b63ffffffff1690805490604084019161150783614768565b60201b67ffffffff000000001693606086019461152386614768565b60401b6bffffffff000000000000000016966080019661154288614768565b60601b6fffffffff00000000000000000000000016916115618a614759565b60801b71ffff0000000000000000000000000000000016936115828c614759565b60901b73ffff00000000000000000000000000000000000016957fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016177fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff16177fffffffffffffffffffffffffffff0000ffffffffffffffffffffffffffffffff16177fffffffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffff1617171781556116358761474c565b81547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690151560a01b74ff0000000000000000000000000000000000000000161790556040519661168690614779565b63ffffffff16875261169790614779565b63ffffffff1660208701526116ab90614779565b63ffffffff1660408601526116bf90614779565b63ffffffff1660608501526116d39061478a565b61ffff1660808401526116e59061478a565b61ffff1660a08301526116f790613c79565b151560c082015260e07ffae1e296719dac5269c3886fb5002bb29bf17ae403060c6eb063a55abaaa104191a26001016113d0565b60248c877f12332265000000000000000000000000000000000000000000000000000000008252600452fd5b60248c61ffff61176686614759565b7f95f3517a00000000000000000000000000000000000000000000000000000000835216600452fd5b8a61ffff611766602493614759565b60248a857f12332265000000000000000000000000000000000000000000000000000000008252600452fd5b7f1e670e4b000000000000000000000000000000000000000000000000000000008752600452602486fd5b50346106685760406003193601126106685760043567ffffffffffffffff81116107f057611827903690600401613e58565b90611830613b65565b9173ffffffffffffffffffffffffffffffffffffffff6001541633141580611a84575b611a585773ffffffffffffffffffffffffffffffffffffffff8316908115611a3057845b818110611882578580f35b73ffffffffffffffffffffffffffffffffffffffff6118aa6118a5838588614799565b614536565b166040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152602081602481855afa908115611a255788916119f2575b50806118ff575b5050600101611877565b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000602080830191825273ffffffffffffffffffffffffffffffffffffffff8a16602484015260448084018590528352918a9190611960606482613d25565b519082865af1156119e75787513d6119de5750813b155b6119b25790847f508d7d183612c18fc339b42618912b9fa3239f631dd7ec0671f950200a0fa66e602060019594604051908152a390386118f5565b602488837f5274afe7000000000000000000000000000000000000000000000000000000008252600452fd5b60011415611977565b6040513d89823e3d90fd5b905060203d8111611a1e575b611a088183613d25565b60208260009281010312610668575051386118ee565b503d6119fe565b6040513d8a823e3d90fd5b6004857f8579befe000000000000000000000000000000000000000000000000000000008152fd5b6024847fcb1afbd700000000000000000000000000000000000000000000000000000000815233600452fd5b5073ffffffffffffffffffffffffffffffffffffffff600c5416331415611853565b5034610668578060031936011261066857604051906006548083528260208101600684526020842092845b818110611bb8575050611ae692500383613d25565b8151611b0a611af482613eba565b91611b026040519384613d25565b808352613eba565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602083019301368437805b8451811015611b69578067ffffffffffffffff611b56600193886145c2565b5116611b6282866145c2565b5201611b37565b50925090604051928392602084019060208552518091526040840192915b818110611b95575050500390f35b825167ffffffffffffffff16845285945060209384019390920191600101611b87565b8454835260019485019487945060209093019201611ad1565b50346106685760206003193601126106685760043573ffffffffffffffffffffffffffffffffffffffff81168091036107f057611c0c614bc5565b7fffffffffffffffffffffffff00000000000000000000000000000000000000006003547fbaff46844acf36d6ee996f489a1a288709c4542bd33cd557770afd267d5812096040805173ffffffffffffffffffffffffffffffffffffffff84168152856020820152a1161760035580f35b503461066857602060031936011261066857611cb1611c9d610509613bc0565b604051918291602083526020830190613c1a565b0390f35b5034610668576020600319360112610668577f307cf716eade81675bea3ccb6917b0f91baa2160056765d9a83d76f819caf06a6020611cf2613a8d565b611cfa614bc5565b6002547fffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffff77ffffffff0000000000000000000000000000000000000000808460401c16169116176002557fffffffff0000000000000000000000000000000000000000000000000000000060405191168152a180f35b503461066857606060031936011261066857611d8a613b1f565b90611d93613b65565b6044359273ffffffffffffffffffffffffffffffffffffffff841680850361111857611dbd614bc5565b73ffffffffffffffffffffffffffffffffffffffff82168015611ed35794611ecd917f3f1036e85d016a93254a0b1415844f79b85424959d90ae5ad51ce8f4533fe70195967fffffffffffffffffffffffff0000000000000000000000000000000000000000600254161760025573ffffffffffffffffffffffffffffffffffffffff85167fffffffffffffffffffffffff0000000000000000000000000000000000000000600a541617600a557fffffffffffffffffffffffff0000000000000000000000000000000000000000600c541617600c556040519384938491604091949373ffffffffffffffffffffffffffffffffffffffff809281606087019816865216602085015216910152565b0390a180f35b6004857f14c880ca000000000000000000000000000000000000000000000000000000008152fd5b50346106685767ffffffffffffffff611f1336613de9565b929091611f1e614bc5565b1691611f37836000526007602052604060002054151590565b156110ec578284526008602052611f6660056040862001611f59368486613d66565b6020815191012090615715565b15611fab57907f52d00ee4d9bd51b40168f2afc5848837288ce258784ad914278791464b3f4d7691611fa5604051928392602084526020840191613f7b565b0390a280f35b82611fef836040519384937f74f23c7c0000000000000000000000000000000000000000000000000000000085526004850152604060248501526044840191613f7b565b0390fd5b50346106685760206003193601126106685767ffffffffffffffff612016613bc0565b168152600860205261202d6005604083200161551c565b80517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe061207261205c83613eba565b9261206a6040519485613d25565b808452613eba565b01835b818110612149575050825b82518110156120c65780612096600192856145c2565b51855260096020526120aa60408620614658565b6120b482856145c2565b526120bf81846145c2565b5001612080565b81846040519182916020830160208452825180915260408401602060408360051b870101940192905b8282106120fe57505050500390f35b91936020612139827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc060019597998495030186528851613c1a565b96019201920185949391926120ef565b806060602080938601015201612075565b50346106685760206003193601126106685760043567ffffffffffffffff81116107f057806004019060a06003198236030112610a40576121996145a9565b506040516020936121aa8583613d25565b80825260848301916121bb83614536565b73ffffffffffffffffffffffffffffffffffffffff807f00000000000000000000000000000000000000000000000000000000000000001691160361264d57602484019477ffffffffffffffff0000000000000000000000000000000061222187614557565b60801b16604051907f2cbc26bb0000000000000000000000000000000000000000000000000000000082526004820152878160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa9081156125d2578491612630575b506126085767ffffffffffffffff6122b487614557565b166122cc816000526007602052604060002054151590565b156125dd578773ffffffffffffffffffffffffffffffffffffffff60025416916024604051809481937fa8d87a3b00000000000000000000000000000000000000000000000000000000835260048301525afa80156125d257849061258a575b73ffffffffffffffffffffffffffffffffffffffff915016330361255e576064850135946123668661235d87614536565b6106918a614557565b73ffffffffffffffffffffffffffffffffffffffff600354169182612443575b886124136105098a8a7ff33bc26b4413b0e7f19f1ea739fdf99098c0061f1f87d954b11f5293fad9ae1067ffffffffffffffff8c6123c78461048e87614557565b6105016123dc6123d687614557565b92614536565b6040805173ffffffffffffffffffffffffffffffffffffffff90921682523360208301528101959095529116929081906060820190565b9061241c614f59565b6040519261242984613cd1565b835281830152611cb1604051928284938452830190613e2e565b823b1561070d57918791858094604051968795869485937fa8027c0f00000000000000000000000000000000000000000000000000000000855260048501608090528061248f91615416565b6084860160a090526101248601906124a692613f7b565b916124b090613bd7565b67ffffffffffffffff1660a48501526044016124cb90613b88565b73ffffffffffffffffffffffffffffffffffffffff1660c48401528b60e48401526124f58b613b88565b73ffffffffffffffffffffffffffffffffffffffff1661010484015283602484015282810360031901604484015261252c91613c1a565b8a606483015203925af1801561066b57612549575b808080612386565b612554828092613d25565b6106685780612541565b6024837f728fe07b00000000000000000000000000000000000000000000000000000000815233600452fd5b508781813d83116125cb575b6125a08183613d25565b81010312611118576125c673ffffffffffffffffffffffffffffffffffffffff91613f5a565b61232c565b503d612596565b6040513d86823e3d90fd5b7fa9902c7e000000000000000000000000000000000000000000000000000000008452600452602483fd5b6004837f53ad11d8000000000000000000000000000000000000000000000000000000008152fd5b6126479150883d8a1161079f576107918183613d25565b3861229d565b5073ffffffffffffffffffffffffffffffffffffffff6107c7602493614536565b5034610668578060031936011261066857602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b5034610668578060031936011261066857602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b503461066857602060031936011261066857602061272f67ffffffffffffffff61271b613bc0565b166000526007602052604060002054151590565b6040519015158152f35b5034610668578060031936011261066857805473ffffffffffffffffffffffffffffffffffffffff811633036127d8577fffffffffffffffffffffffff000000000000000000000000000000000000000060015491338284161760015516825573ffffffffffffffffffffffffffffffffffffffff3391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b6004827f02b543c6000000000000000000000000000000000000000000000000000000008152fd5b5034610668578060031936011261066857600254600a54600c546040805173ffffffffffffffffffffffffffffffffffffffff94851681529284166020840152921691810191909152606090f35b50346106685761285d36613de9565b61286993929193614bc5565b67ffffffffffffffff821661288b816000526007602052604060002054151590565b156128aa57506128a792936128a1913691613d66565b90614c10565b80f35b7f1e670e4b000000000000000000000000000000000000000000000000000000008452600452602483fd5b5034610668576040600319360112610668576128ef613bc0565b906024359067ffffffffffffffff821161066857602061272f846129163660048701613dcb565b9061456c565b5034610668576020600319360112610668576004359067ffffffffffffffff82116106685781600401906101006003198436030112610668578060405161296281613c86565b528060405161297081613c86565b52606483013560c48401936129a061299a61299561298e88886144e5565b3691613d66565b614879565b83614934565b9360848201956129af87614536565b73ffffffffffffffffffffffffffffffffffffffff807f000000000000000000000000000000000000000000000000000000000000000016911603612ff857602483019377ffffffffffffffff00000000000000000000000000000000612a1586614557565b60801b16604051907f2cbc26bb000000000000000000000000000000000000000000000000000000008252600482015260208160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa9081156119e7578791612fd9575b50612fb15767ffffffffffffffff612aa986614557565b16612ac1816000526007602052604060002054151590565b15612f8657602073ffffffffffffffffffffffffffffffffffffffff60025416916044604051809481937f83826b2b00000000000000000000000000000000000000000000000000000000835260048301523360248301525afa9081156119e7578791612f67575b5015612f3b57612b3885614557565b92612b4e60a486019461291661298e87856144e5565b15612ef457612b6f88612b608b614536565b612b6989614557565b9061532d565b73ffffffffffffffffffffffffffffffffffffffff600354169283612d22575b505050505060440191612ba183614536565b612baa83614557565b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016803b1561111857608484928367ffffffffffffffff9373ffffffffffffffffffffffffffffffffffffffff60405197889687957f74fd18ac000000000000000000000000000000000000000000000000000000008752837f00000000000000000000000000000000000000000000000000000000000000001660048801521660248601528c60448601521660648401525af1801561066b57612d0d575b5050608067ffffffffffffffff60209573ffffffffffffffffffffffffffffffffffffffff612cd9612cd36104c97ffc5e3a5bddc11d92c2dc20fae6f7d5eb989f056be35239f7de7e86150609abc097614557565b96614536565b816040519716875233898801521660408601528560608601521692a260405190612d0282613c86565b815260405190518152f35b612d18828092613d25565b6106685780612c7e565b833b15612ef057878795938195938c93604051988997889687957f6371157400000000000000000000000000000000000000000000000000000000875260048701606090528d612d728780615416565b60648a0161010090526101648a0190612d8a92613f7b565b94612d9490613bd7565b67ffffffffffffffff166084890152604401612daf90613b88565b73ffffffffffffffffffffffffffffffffffffffff1660a488015260c4870152612dd890613b88565b73ffffffffffffffffffffffffffffffffffffffff1660e4860152612dfd9084615416565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c86840301610104870152612e329291613f7b565b90612e3d9083615416565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c85840301610124860152612e729291613f7b565b9060e48a01612e8091615416565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c84840301610144850152612eb59291613f7b565b8b602483015282604483015203925af180156125d257908491612edb575b808080612b8f565b81612ee591613d25565b610a40578238612ed3565b8780fd5b83612efe916144e5565b611fef6040519283927f24eb47e5000000000000000000000000000000000000000000000000000000008452602060048501526024840191613f7b565b6024867f728fe07b00000000000000000000000000000000000000000000000000000000815233600452fd5b612f80915060203d60201161079f576107918183613d25565b38612b29565b7fa9902c7e000000000000000000000000000000000000000000000000000000008752600452602486fd5b6004867f53ad11d8000000000000000000000000000000000000000000000000000000008152fd5b612ff2915060203d60201161079f576107918183613d25565b38612a92565b60248573ffffffffffffffffffffffffffffffffffffffff6107c78a614536565b5034610668578060031936011261066857602073ffffffffffffffffffffffffffffffffffffffff60035416604051908152f35b5034610668576040600319360112610668576004359067ffffffffffffffff821161066857816004019061010060031984360301126106685761308e613ac1565b918160405161309c81613c86565b5260648401359360c48101936130c16130bb61299561298e88876144e5565b87614934565b9460848301966130d088614536565b73ffffffffffffffffffffffffffffffffffffffff807f00000000000000000000000000000000000000000000000000000000000000001691160361359f57602484019477ffffffffffffffff0000000000000000000000000000000061313687614557565b60801b16604051907f2cbc26bb000000000000000000000000000000000000000000000000000000008252600482015260208160248173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000165afa908115611a25578891613580575b506135585767ffffffffffffffff6131ca87614557565b166131e2816000526007602052604060002054151590565b1561352d57602073ffffffffffffffffffffffffffffffffffffffff60025416916044604051809481937f83826b2b00000000000000000000000000000000000000000000000000000000835260048301523360248301525afa908115611a2557889161350e575b50156134e25761325986614557565b9361326f60a487019561291661298e88866144e5565b156134d8577fffffffff00000000000000000000000000000000000000000000000000000000169081156134bd576132b9896132aa8c614536565b6132b38a614557565b906153a6565b73ffffffffffffffffffffffffffffffffffffffff6003541693846132ec575b50505050505060440191612ba183614536565b843b156134b957868995938c959387938b6040519a8b998a9889977f63711574000000000000000000000000000000000000000000000000000000008952600489016060905261333c8780615416565b60648b0161010090526101648b019061335492613f7b565b9461335e90613bd7565b67ffffffffffffffff1660848a015260440161337990613b88565b73ffffffffffffffffffffffffffffffffffffffff1660a489015260c48801526133a290613b88565b73ffffffffffffffffffffffffffffffffffffffff1660e48701526133c79084615416565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c878403016101048801526133fc9291613f7b565b906134079083615416565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c8684030161012487015261343c9291613f7b565b9060e48b0161344a91615416565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c8584030161014486015261347f9291613f7b565b908c6024840152604483015203925af180156125d2576134a4575b80808080806132d9565b926134b28160449395613d25565b929061349a565b8880fd5b6134d3896134ca8c614536565b612b698a614557565b6132b9565b612efe85836144e5565b6024877f728fe07b00000000000000000000000000000000000000000000000000000000815233600452fd5b613527915060203d60201161079f576107918183613d25565b3861324a565b7fa9902c7e000000000000000000000000000000000000000000000000000000008852600452602487fd5b6004877f53ad11d8000000000000000000000000000000000000000000000000000000008152fd5b613599915060203d60201161079f576107918183613d25565b386131b3565b60248673ffffffffffffffffffffffffffffffffffffffff6107c78b614536565b5034610668578060031936011261066857602060405160ff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b503461066857604060031936011261066857613618613bc0565b602435918215158303610668576101406136db6136358585614462565b61368b60409392935180946fffffffffffffffffffffffffffffffff6080809282815116855263ffffffff6020820151166020860152604081015115156040860152826060820151166060860152015116910152565b60a08301906fffffffffffffffffffffffffffffffff6080809282815116855263ffffffff6020820151166020860152604081015115156040860152826060820151166060860152015116910152565bf35b5034610668576020600319360112610668576020906136fa613b1f565b905073ffffffffffffffffffffffffffffffffffffffff807f0000000000000000000000000000000000000000000000000000000000000000169116146040519015158152f35b5034610668578060031936011261066857602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50346106685760c0600319360112610668576137ac613b1f565b506137b5613ba9565b6137bd613b42565b50608435917fffffffff00000000000000000000000000000000000000000000000000000000831683036106685760a4359067ffffffffffffffff82116106685760a063ffffffff8061ffff613822888861381b3660048b01613bec565b50506142b2565b95926040979194975197885216602087015216604085015216606083015215156080820152f35b503461066857806003193601126106685750611cb160405161386c604082613d25565b601a81527f4c6f636b52656c65617365546f6b656e506f6f6c20322e302e300000000000006020820152604051918291602083526020830190613c1a565b50346106685760c0600319360112610668576138c4613b1f565b6138cc613ba9565b906064357fffffffff00000000000000000000000000000000000000000000000000000000811681036111185760843567ffffffffffffffff811161070d57613919903690600401613bec565b9160a435936002851015610676576139349560443591613fba565b90604051918291602083016020845282518091526020604085019301915b818110613960575050500390f35b825173ffffffffffffffffffffffffffffffffffffffff16845285945060209384019390920191600101613952565b9050346107f05760206003193601126107f0576020907fffffffff000000000000000000000000000000000000000000000000000000006139ce613a8d565b167faff2afbf000000000000000000000000000000000000000000000000000000008114908115613a63575b8115613a39575b8115613a0f575b5015158152f35b7f01ffc9a70000000000000000000000000000000000000000000000000000000091501483613a08565b7f0e64dd290000000000000000000000000000000000000000000000000000000081149150613a01565b7f940a154200000000000000000000000000000000000000000000000000000000811491506139fa565b600435907fffffffff0000000000000000000000000000000000000000000000000000000082168203613abc57565b600080fd5b602435907fffffffff0000000000000000000000000000000000000000000000000000000082168203613abc57565b604435907fffffffff0000000000000000000000000000000000000000000000000000000082168203613abc57565b6004359073ffffffffffffffffffffffffffffffffffffffff82168203613abc57565b6064359073ffffffffffffffffffffffffffffffffffffffff82168203613abc57565b6024359073ffffffffffffffffffffffffffffffffffffffff82168203613abc57565b359073ffffffffffffffffffffffffffffffffffffffff82168203613abc57565b6024359067ffffffffffffffff82168203613abc57565b6004359067ffffffffffffffff82168203613abc57565b359067ffffffffffffffff82168203613abc57565b9181601f84011215613abc5782359167ffffffffffffffff8311613abc5760208381860195010111613abc57565b919082519283825260005b848110613c645750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006020809697860101520116010190565b80602080928401015182828601015201613c25565b35908115158203613abc57565b6020810190811067ffffffffffffffff821117613ca257604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040810190811067ffffffffffffffff821117613ca257604052565b60a0810190811067ffffffffffffffff821117613ca257604052565b60e0810190811067ffffffffffffffff821117613ca257604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117613ca257604052565b92919267ffffffffffffffff8211613ca25760405191613dae601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200184613d25565b829481845281830111613abc578281602093846000960137010152565b9080601f83011215613abc57816020613de693359101613d66565b90565b906040600319830112613abc5760043567ffffffffffffffff81168103613abc57916024359067ffffffffffffffff8211613abc57613e2a91600401613bec565b9091565b613de6916020613e478351604084526040840190613c1a565b920151906020818403910152613c1a565b9181601f84011215613abc5782359167ffffffffffffffff8311613abc576020808501948460051b010111613abc57565b9181601f84011215613abc5782359167ffffffffffffffff8311613abc576020808501948460081b010111613abc57565b67ffffffffffffffff8111613ca25760051b60200190565b81810292918115918404141715613ee557565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8115613f1e570490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b91908203918211613ee557565b519073ffffffffffffffffffffffffffffffffffffffff82168203613abc57565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b92959390919473ffffffffffffffffffffffffffffffffffffffff6003541695861561429057809760028710156142615773ffffffffffffffffffffffffffffffffffffffff9861411b957fffffffff0000000000000000000000000000000000000000000000000000000093896142375767ffffffffffffffff8216600052600b6020526040600020906040519161405283613d09565b549163ffffffff8316815263ffffffff8360201c16602082015263ffffffff8360401c16604082015263ffffffff8360601c16606082015260c061ffff8460801c169182608082015260ff60a082019561ffff8160901c16875260a01c16151591829101526141e3575b50505067ffffffffffffffff905b6040519b8c997f06b859ef000000000000000000000000000000000000000000000000000000008b521660048a0152166024880152604487015216606485015260c0608485015260c4840191613f7b565b928180600095869560a483015203915afa9182156141d657819261413e57505090565b9091503d8083833e6141508183613d25565b810190602081830312610a405780519067ffffffffffffffff8211611118570181601f82011215610a405780519061418782613eba565b936141956040519586613d25565b82855260208086019360051b8301019384116106685750602001905b8282106141be5750505090565b602080916141cb84613f5a565b8152019101906141b1565b50604051903d90823e3d90fd5b92935067ffffffffffffffff928587161561421f575061271061420e61ffff61421594511683613ed2565b0490613f4d565b915b9038806140bc565b614231925061420e6127109183613ed2565b91614217565b67ffffffffffffffff91925061425b9061425561299536898b613d66565b90614934565b916140ca565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b50505050505050506040516142a6602082613d25565b60008152600036813790565b67ffffffffffffffff909291926142f07fffffffff0000000000000000000000000000000000000000000000000000000060025460401b1685614a41565b16600052600b60205260406000206040519061430b82613d09565b549163ffffffff83169384835263ffffffff8460201c169384602085015263ffffffff8160401c169182604086015263ffffffff8260601c169081606087015261ffff8360801c169586608082015260ff61ffff8560901c16948560a084015260a01c16159060c082159101526143b8577fffffffff00000000000000000000000000000000000000000000000000000000166143ad57505093929190600190565b959493509160019150565b5050505092505050600090600090600090600090600090565b604051906143de82613ced565b60006080838281528260208201528260408201528260608201520152565b9060405161440981613ced565b60806001829460ff81546fffffffffffffffffffffffffffffffff8116865263ffffffff81861c16602087015260a01c161515604085015201546fffffffffffffffffffffffffffffffff81166060840152811c910152565b67ffffffffffffffff916144746143d1565b5061447d6143d1565b506144b157166000526008602052604060002090613de66144a560026144aa6144a5866143fc565b614b28565b94016143fc565b16908160005260046020526144cc6144a560406000206143fc565b916000526005602052613de66144a560406000206143fc565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215613abc570180359067ffffffffffffffff8211613abc57602001918136038313613abc57565b3573ffffffffffffffffffffffffffffffffffffffff81168103613abc5790565b3567ffffffffffffffff81168103613abc5790565b9067ffffffffffffffff613de692166000526008602052600560406000200190602081519101209060019160005201602052604060002054151590565b604051906145b682613cd1565b60606020838281520152565b80518210156145d65760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90600182811c9216801561464e575b602083101461461f57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f1691614614565b906040519182600082549261466c84614605565b80845293600181169081156146da5750600114614693575b5061469192500383613d25565b565b90506000929192526020600020906000915b8183106146be5750509060206146919282010138614684565b60209193508060019154838589010152019101909184926146a5565b602093506146919592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091501682840152151560051b82010138614684565b67ffffffffffffffff166000526008602052613de66004604060002001614658565b91908110156145d65760081b0190565b358015158103613abc5790565b3561ffff81168103613abc5790565b3563ffffffff81168103613abc5790565b359063ffffffff82168203613abc57565b359061ffff82168203613abc57565b91908110156145d65760051b0190565b35906fffffffffffffffffffffffffffffffff82168203613abc57565b9190826060910312613abc576040516060810181811067ffffffffffffffff821117613ca257604052604061481981839561480081613c79565b855261480e602082016147a9565b6020860152016147a9565b910152565b6fffffffffffffffffffffffffffffffff61485c6040809361483f81613c79565b1515865283614850602083016147a9565b166020870152016147a9565b16910152565b81811061486d575050565b60008155600101614862565b805180156148e9576020036148ab578051602082810191830183900312613abc57519060ff82116148ab575060ff1690565b611fef906040519182917f953576f7000000000000000000000000000000000000000000000000000000008352602060048401526024830190613c1a565b50507f000000000000000000000000000000000000000000000000000000000000000090565b9060ff8091169116039060ff8211613ee557565b60ff16604d8111613ee557600a0a90565b907f00000000000000000000000000000000000000000000000000000000000000009060ff82169060ff811692828414614a3a57828411614a1057906149799161490f565b91604d60ff84161180156149d7575b6149a15750509061499b613de692614923565b90613ed2565b9091507fa9cb113d0000000000000000000000000000000000000000000000000000000060005260045260245260445260646000fd5b506149e183614923565b8015613f1e577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048411614988565b614a199161490f565b91604d60ff8416116149a157505090614a34613de692614923565b90613f14565b5050505090565b7fffffffff000000000000000000000000000000000000000000000000000000008116908115614b2357614a7481615252565b7dffff00000000000000000000000000000000000000000000000000000000601082811c9085901c1616614b235761ffff8360e01c168015918215614b12575b5050614abe575050565b7fffffffff0000000000000000000000000000000000000000000000000000000092507fdf63778f000000000000000000000000000000000000000000000000000000006000526004521660245260446000fd5b60e01c61ffff161090503880614ab4565b505050565b614b306143d1565b506fffffffffffffffffffffffffffffffff6060820151166fffffffffffffffffffffffffffffffff8083511691614b8d6020850193614b87614b7a63ffffffff87511642613f4d565b8560808901511690613ed2565b90615245565b80821015614ba657505b16825263ffffffff4216905290565b9050614b97565b90816020910312613abc57518015158103613abc5790565b73ffffffffffffffffffffffffffffffffffffffff600154163303614be657565b7f2b5c74de0000000000000000000000000000000000000000000000000000000060005260046000fd5b90805115614e505767ffffffffffffffff81516020830120921691826000526008602052614c458160056040600020016158a9565b15614e0c5760005260096020526040600020815167ffffffffffffffff8111613ca257614c728254614605565b601f8111614dda575b506020601f8211600114614d145791614cee827f7d628c9a1796743d365ab521a8b2a4686e419b3269919dc9145ea2ce853b54ea9593614d0495600091614d09575b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b9055604051918291602083526020830190613c1a565b0390a2565b905084015138614cbd565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082169083600052806000209160005b818110614dc2575092614d049492600192827f7d628c9a1796743d365ab521a8b2a4686e419b3269919dc9145ea2ce853b54ea989610614d8b575b5050811b019055611c9d565b8501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690553880614d7f565b9192602060018192868a015181550194019201614d44565b614e0690836000526020600020601f840160051c81019160208510610e9057601f0160051c0190614862565b38614c7b565b5090611fef6040519283927f393b8ad20000000000000000000000000000000000000000000000000000000084526004840152604060248401526044830190613c1a565b7f14c880ca0000000000000000000000000000000000000000000000000000000060005260046000fd5b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016803b15613abc5767ffffffffffffffff906064604051809481937fa36a7fee0000000000000000000000000000000000000000000000000000000083526000978896879373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600487015216602485015260448401525af1801561066b57614f4c575050565b81614f5691613d25565b50565b60405160ff7f000000000000000000000000000000000000000000000000000000000000000016602082015260208152613de6604082613d25565b815191929115615116576fffffffffffffffffffffffffffffffff6040840151166fffffffffffffffffffffffffffffffff602085015116106150b35761469191925b805182547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690151560a01b74ff0000000000000000000000000000000000000000161782556020810151825460409290920151608090811b7fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff9290921691821760018501557fffffffffffffffffffffffff0000000000000000000000000000000000000000909216174290911b73ffffffff0000000000000000000000000000000016179055565b606483615114604051917f8020d12400000000000000000000000000000000000000000000000000000000835260048301906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565bfd5b6fffffffffffffffffffffffffffffffff604084015116158015906151a5575b615144576146919192614fd7565b606483615114604051917fd68af9cc00000000000000000000000000000000000000000000000000000000835260048301906fffffffffffffffffffffffffffffffff60408092805115158552826020820151166020860152015116910152565b506fffffffffffffffffffffffffffffffff6020840151161515615136565b906127109167ffffffffffffffff6151de60208301614557565b166000908152600b60205260409020917fffffffff00000000000000000000000000000000000000000000000000000000161561522f57606061ffff61522b935460901c16910135613ed2565b0490565b606061ffff61522b935460801c16910135613ed2565b91908201809211613ee557565b7fffffffff000000000000000000000000000000000000000000000000000000008116908115615329577dffff000000000000000000000000000000000000000000000000000000008116156153205760ff60015b169060f01c806152ea575b506001036152bd5750565b7fc512f96c0000000000000000000000000000000000000000000000000000000060005260045260246000fd5b60005b601081106152fb57506152b2565b6001811b821661530e575b6001016152ed565b9160018101809111613ee55791615306565b60ff60006152a7565b5050565b9167ffffffffffffffff7f50f6fbee3ceedce6b7fd7eaef18244487867e6718aec7208187efb6b7908c14c921692836000526008602052615376818360026040600020016158fe565b6040805173ffffffffffffffffffffffffffffffffffffffff909216825260208201929092529081908101614d04565b91909167ffffffffffffffff83169283600052600560205260ff60406000205460a01c161561540b5750907fc6735cd4fa2bbe7b203b1682936e6ee61bc1702464bbbd12abb6630229d9a5f991836000526005602052615376818360406000206158fe565b90614691935061532d565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe182360301811215613abc57016020813591019167ffffffffffffffff8211613abc578136038313613abc57565b9167ffffffffffffffff7fff0133389f9bb82d5b9385826160eaf2328039f6fa950eeb8cf0836da8178944921692836000526008602052615376818360406000206158fe565b91909167ffffffffffffffff83169283600052600460205260ff60406000205460a01c16156155115750907f28d6c52e2b0b7587b0d195539fbe6af984b28791aca4d2cc0844244e38bce29e91836000526004602052615376818360406000206158fe565b906146919350615466565b906040519182815491828252602082019060005260206000209260005b81811061554e57505061469192500383613d25565b8454835260019485019487945060209093019201615539565b80548210156145d65760005260206000200190600090565b600081815260076020526040902054801561570e577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818111613ee557600654907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201918211613ee55781810361569f575b5050506006548015615670577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0161562d816006615567565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82549160031b1b19169055600655600052600760205260006040812055600190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b6156f66156b06156c1936006615567565b90549060031b1c9283926006615567565b81939154907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060031b92831b921b19161790565b905560005260076020526040600020553880806155f4565b5050600090565b9060018201918160005282602052604060002054801515600014615840577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101818111613ee5578254907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201918211613ee557818103615809575b50505080548015615670577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01906157ca8282615567565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82549160031b1b191690555560005260205260006040812055600190565b6158296158196156c19386615567565b90549060031b1c92839286615567565b905560005283602052604060002055388080615792565b50505050600090565b806000526007602052604060002054156000146158a35760065468010000000000000000811015613ca25761588a6156c18260018594016006556006615567565b9055600654906000526007602052604060002055600190565b50600090565b600082815260018201602052604090205461570e5780549068010000000000000000821015613ca257826158e76156c1846001809601855584615567565b905580549260005201602052604060002055600190565b9182549060ff8260a01c16158015615bb2575b615bac576fffffffffffffffffffffffffffffffff8216916001850190815461595663ffffffff6fffffffffffffffffffffffffffffffff83169360801c1642613f4d565b9081615b0e575b5050848110615ac257508383106159b757505061598c6fffffffffffffffffffffffffffffffff928392613f4d565b16167fffffffffffffffffffffffffffffffff00000000000000000000000000000000825416179055565b9190915460801c928315615a5657816159cf91613f4d565b927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810190808211613ee557615a1d615a229273ffffffffffffffffffffffffffffffffffffffff96615245565b613f14565b7fd0c8d23a000000000000000000000000000000000000000000000000000000006000526004526024521660445260646000fd5b73ffffffffffffffffffffffffffffffffffffffff83837fd0c8d23a000000000000000000000000000000000000000000000000000000006000527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6004526024521660445260646000fd5b828573ffffffffffffffffffffffffffffffffffffffff927f1a76572a000000000000000000000000000000000000000000000000000000006000526004526024521660445260646000fd5b828692939611615b8257615b2992614b879160801c90613ed2565b80841015615b7d5750825b85547fffffffffffffffffffffffff00000000ffffffffffffffffffffffffffffffff164260801b73ffffffff000000000000000000000000000000001617865592388061595d565b615b34565b7f9725942a0000000000000000000000000000000000000000000000000000000060005260046000fd5b50505050565b50821561591156fea164736f6c634300081a000a' as const +// generate:end diff --git a/ccip-sdk/src/cct/evm/index.test.ts b/ccip-sdk/src/cct/evm/index.test.ts new file mode 100644 index 000000000..015467a27 --- /dev/null +++ b/ccip-sdk/src/cct/evm/index.test.ts @@ -0,0 +1,953 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, id } from 'ethers' + +import { CCIPWalletInvalidError } from '../../errors/index.ts' +import { interfaces } from '../../evm/const.ts' +import type { EVMChain } from '../../evm/index.ts' +import { ChainFamily } from '../../networks.ts' +import { CCTContractTypeInvalidError, CCTParamsInvalidError } from '../errors.ts' +import { EVMTokenManager } from './index.ts' + +const TOKEN = '0x' + '11'.repeat(20) +const POOL = '0x' + '22'.repeat(20) +const ROUTER = '0x' + '33'.repeat(20) +const TAR = '0x' + '44'.repeat(20) +const REGISTRY_MODULE = '0x' + '55'.repeat(20) +const ADMIN = '0x' + '66'.repeat(20) +// Distinct from ADMIN/REGISTRY_MODULE on purpose: sharing a value would let an assertion pass +// against the wrong address. +const CURRENT_ADMIN = '0x' + '77'.repeat(20) +const NEW_ADMIN = '0x' + '88'.repeat(20) + +/** Encodes a `getTokenConfig` result the way the on-chain TAR would. */ +function encodeTokenConfig(administrator: string, pendingAdministrator = ZeroAddress) { + return interfaces.TokenAdminRegistry.encodeFunctionResult('getTokenConfig', [ + [administrator, pendingAdministrator, ZeroAddress], + ]) +} + +/** Minimal EVMChain stub — only the members EVMTokenManager touches. */ +function stubChain(overrides: Partial = {}, poolVersion = '1.5.1'): EVMChain { + return { + provider: { call: async () => encodeTokenConfig(CURRENT_ADMIN) }, + logger: { debug() {}, info() {}, warn() {}, error() {} }, + getTokenAdminRegistryFor: (_address: string) => Promise.resolve(TAR), + typeAndVersion: (address: string) => + Promise.resolve( + address === REGISTRY_MODULE + ? ['RegistryModuleOwnerCustom', '1.6.0'] + : ['BurnMintTokenPool', poolVersion], + ), + nextNonce: async () => 0, + rollbackNonce: () => {}, + ...overrides, + } as unknown as EVMChain +} + +const HASH = '0x' + 'ab'.repeat(32) + +/** Fake ethers Signer whose broadcast resolves to a confirmed receipt. */ +function fakeSigner(address = TOKEN) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ hash: HASH, wait: () => Promise.resolve({ status: 1 }) }), + } +} + +const REGISTER_ADMIN_SELECTOR = id('registerAdminViaOwner(address)').slice(0, 10) +const IS_REGISTRY_MODULE_SELECTOR = + interfaces.TokenAdminRegistry.getFunction('isRegistryModule')!.selector +const GET_TOKEN_CONFIG_SELECTOR = + interfaces.TokenAdminRegistry.getFunction('getTokenConfig')!.selector +const OWNER_SELECTOR = new Interface(['function owner() view returns (address)']).getFunction( + 'owner', +)!.selector + +/** + * Selector-aware `provider.call` for `registerAdmin`'s on-chain checks: the module is + * registered, the token is unregistered, and `owner()` resolves to `ADMIN`. + */ +function registerAdminProvider() { + return { + call: async (tx: { data?: string }) => { + const sel = (tx.data ?? '0x').slice(0, 10) + if (sel === IS_REGISTRY_MODULE_SELECTOR) + return interfaces.TokenAdminRegistry.encodeFunctionResult('isRegistryModule', [true]) + if (sel === GET_TOKEN_CONFIG_SELECTOR) + return interfaces.TokenAdminRegistry.encodeFunctionResult('getTokenConfig', [ + [ZeroAddress, ZeroAddress, ZeroAddress], + ]) + if (sel === OWNER_SELECTOR) + return new Interface(['function owner() view returns (address)']).encodeFunctionResult( + 'owner', + [ADMIN], + ) + throw new Error(`registerAdminProvider: unexpected call, selector ${sel}`) + }, + } +} + +const SET_POOL_SELECTOR = id('setPool(address,address)').slice(0, 10) +const TRANSFER_ADMIN_ROLE_SELECTOR = id('transferAdminRole(address,address)').slice(0, 10) +const EXPECTED_TRANSFER_ADMIN = new Interface([ + 'function transferAdminRole(address localToken, address newAdmin)', +]).encodeFunctionData('transferAdminRole', [TOKEN, NEW_ADMIN]) +const EXPECTED_DATA = new Interface([ + 'function setPool(address localToken, address pool)', +]).encodeFunctionData('setPool', [TOKEN, POOL]) +const EXPECTED_TRANSFER = new Interface([ + 'function transferOwnership(address to)', +]).encodeFunctionData('transferOwnership', [TOKEN]) +const ACCEPT_ADMIN_SELECTOR = id('acceptAdminRole(address)').slice(0, 10) +const EXPECTED_ACCEPT_ADMIN = new Interface([ + 'function acceptAdminRole(address localToken)', +]).encodeFunctionData('acceptAdminRole', [TOKEN]) + +/** Fake provider whose `call` answers `getTokenConfig` with `pendingAdministrator = TOKEN`. */ +function acceptAdminProvider(pendingAdministrator: string) { + return { + call: () => + Promise.resolve( + interfaces.TokenAdminRegistry.encodeFunctionResult('getTokenConfig', [ + [ZeroAddress, pendingAdministrator, ZeroAddress], + ]), + ), + } +} + +describe('EVMTokenManager (cct/evm)', () => { + describe('construction', () => { + it('fromChain wraps an existing chain and exposes its provider', () => { + const chain = stubChain() + const cct = EVMTokenManager.fromChain(chain) + assert.ok(cct instanceof EVMTokenManager) + assert.equal(cct.chain, chain) + assert.equal(cct.provider, chain.provider) + }) + }) + + describe('generateUnsignedRegisterAdmin', () => { + it('encodes registerAdminViaOwner(token) to the registry module', async () => { + const cct = EVMTokenManager.fromChain( + stubChain({ provider: registerAdminProvider() as never }), + ) + const unsigned = await cct.generateUnsignedRegisterAdmin({ + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + sender: ADMIN, + }) + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + const tx = unsigned.transactions[0]! + assert.equal(tx.to, REGISTRY_MODULE) + assert.equal(tx.from, ADMIN) + assert.ok( + tx.data!.startsWith(REGISTER_ADMIN_SELECTOR), + 'data starts with registerAdminViaOwner selector', + ) + }) + + it('rejects an invalid address before any RPC, tagged with the operation', async () => { + let called = false + const cct = EVMTokenManager.fromChain( + stubChain({ + getTokenAdminRegistryFor: () => { + called = true + return Promise.resolve(TAR) + }, + }), + ) + await assert.rejects( + () => + cct.generateUnsignedRegisterAdmin({ + tokenAddress: 'not-an-address', + registryModule: REGISTRY_MODULE, + address: ROUTER, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'registerAdmin' && + err.context.param === 'tokenAddress', + ) + assert.equal(called, false, 'validation fails before TAR discovery') + }) + }) + + describe('registerAdmin', () => { + it('signs and submits, resolving to the confirmed tx hash', async () => { + const cct = EVMTokenManager.fromChain( + stubChain({ provider: registerAdminProvider() as never }), + ) + // `sender` is left off `opts` — `registerAdmin` defaults it to the wallet's own address + // (see `RegisterAdmin.execute`), which must equal `owner()` (ADMIN, per `registerAdminProvider`). + const result = await cct.registerAdmin({ + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + wallet: fakeSigner(ADMIN), + }) + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-signer wallet', async () => { + const cct = EVMTokenManager.fromChain( + stubChain({ provider: registerAdminProvider() as never }), + ) + await assert.rejects( + () => + cct.registerAdmin({ + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + wallet: {}, + }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + + it('rejects a wallet that is not the token owner before any tx is submitted', async () => { + const cct = EVMTokenManager.fromChain( + stubChain({ provider: registerAdminProvider() as never }), + ) + // No explicit `sender` — this is the default `registerAdmin({ ...params, wallet })` shape, + // the exact path the authority check must not skip (it defaults `sender` to the wallet's + // own address, so a wallet that isn't `owner()` is caught here, pre-tx). + await assert.rejects( + () => + cct.registerAdmin({ + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + wallet: fakeSigner(), // TOKEN address, not ADMIN — not the token's owner() + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'registerAdmin' && + err.context.param === 'sender', + ) + }) + }) + + describe('generateUnsignedSetPool', () => { + it('encodes setPool(token, pool) to the discovered TAR', async () => { + const cct = EVMTokenManager.fromChain(stubChain()) + const unsigned = await cct.generateUnsignedSetPool({ + tokenAddress: TOKEN, + poolAddress: POOL, + address: ROUTER, + sender: TOKEN, + }) + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + + const tx = unsigned.transactions[0]! + assert.equal(tx.to, TAR) + assert.equal(tx.from, TOKEN) + assert.ok(tx.data!.startsWith(SET_POOL_SELECTOR), 'data starts with setPool selector') + assert.equal(tx.data, EXPECTED_DATA) + }) + + it('discovers the TAR from the router address', async () => { + let seen: string | undefined + const cct = EVMTokenManager.fromChain( + stubChain({ + getTokenAdminRegistryFor: (address: string) => { + seen = address + return Promise.resolve(TAR) + }, + }), + ) + await cct.generateUnsignedSetPool({ + tokenAddress: TOKEN, + poolAddress: POOL, + address: ROUTER, + }) + assert.equal(seen, ROUTER) + }) + + it('omits `from` when no sender is given', async () => { + const cct = EVMTokenManager.fromChain(stubChain()) + const unsigned = await cct.generateUnsignedSetPool({ + tokenAddress: TOKEN, + poolAddress: POOL, + address: ROUTER, + }) + assert.equal(unsigned.transactions[0]!.from, undefined) + }) + + it('rejects an invalid address before any RPC, tagged with the operation', async () => { + let called = false + const cct = EVMTokenManager.fromChain( + stubChain({ + getTokenAdminRegistryFor: () => { + called = true + return Promise.resolve(TAR) + }, + }), + ) + await assert.rejects( + () => + cct.generateUnsignedSetPool({ + tokenAddress: 'not-an-address', + poolAddress: POOL, + address: ROUTER, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setPool' && + err.context.param === 'tokenAddress', + ) + assert.equal(called, false, 'validation fails before TAR discovery') + }) + }) + + describe('setPool', () => { + it('signs and submits, resolving to the confirmed tx hash', async () => { + const cct = EVMTokenManager.fromChain(stubChain()) + const result = await cct.setPool({ + tokenAddress: TOKEN, + poolAddress: POOL, + address: ROUTER, + wallet: fakeSigner(), + }) + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-signer wallet', async () => { + const cct = EVMTokenManager.fromChain(stubChain()) + await assert.rejects( + () => + cct.setPool({ + tokenAddress: TOKEN, + poolAddress: POOL, + address: ROUTER, + wallet: {}, + }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + }) + + describe('generateUnsignedTransferAdmin', () => { + it('encodes transferAdminRole(token, newAdmin) to the discovered TAR', async () => { + const cct = EVMTokenManager.fromChain(stubChain()) + const unsigned = await cct.generateUnsignedTransferAdmin({ + tokenAddress: TOKEN, + newAdmin: NEW_ADMIN, + address: ROUTER, + sender: CURRENT_ADMIN, + }) + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + + const tx = unsigned.transactions[0]! + assert.equal(tx.to, TAR) + assert.equal(tx.from, CURRENT_ADMIN) + assert.ok( + tx.data!.startsWith(TRANSFER_ADMIN_ROLE_SELECTOR), + 'data starts with transferAdminRole selector', + ) + assert.equal(tx.data, EXPECTED_TRANSFER_ADMIN) + }) + + it('rejects a sender that is not the current registry administrator', async () => { + const cct = EVMTokenManager.fromChain(stubChain()) + await assert.rejects( + () => + cct.generateUnsignedTransferAdmin({ + tokenAddress: TOKEN, + newAdmin: NEW_ADMIN, + address: ROUTER, + sender: NEW_ADMIN, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'transferAdmin' && + err.context.param === 'sender', + ) + }) + }) + + describe('transferAdmin', () => { + it('signs and submits, resolving to the confirmed tx hash', async () => { + const cct = EVMTokenManager.fromChain(stubChain()) + const result = await cct.transferAdmin({ + tokenAddress: TOKEN, + newAdmin: NEW_ADMIN, + address: ROUTER, + sender: CURRENT_ADMIN, + wallet: fakeSigner(CURRENT_ADMIN), + }) + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-signer wallet', async () => { + const cct = EVMTokenManager.fromChain(stubChain()) + await assert.rejects( + () => + cct.transferAdmin({ + tokenAddress: TOKEN, + newAdmin: NEW_ADMIN, + address: ROUTER, + sender: CURRENT_ADMIN, + wallet: {}, + }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + }) + + describe('transferOwnership', () => { + it('probes the pool type/version, then builds transferOwnership to the pool', async () => { + const probed: string[] = [] + const cct = EVMTokenManager.fromChain( + stubChain({ + typeAndVersion: ((address: string) => { + probed.push(address) + return Promise.resolve(['BurnMintTokenPool', '1.5.1', 'BurnMintTokenPool 1.5.1']) + }) as unknown as EVMChain['typeAndVersion'], + }), + ) + const unsigned = await cct.generateUnsignedTransferOwnership({ + poolAddress: POOL, + newOwner: TOKEN, + }) + assert.deepEqual(probed, [POOL]) // resolved the pool's type/version from its own address + assert.equal(unsigned.transactions[0]!.to, POOL) + assert.equal(unsigned.transactions[0]!.data, EXPECTED_TRANSFER) + }) + + it('surfaces an unsupported pool type reported by the probe', async () => { + const cct = EVMTokenManager.fromChain( + stubChain({ + typeAndVersion: (() => + Promise.resolve([ + 'NotATokenPool', + '1.5.1', + 'NotATokenPool 1.5.1', + ])) as unknown as EVMChain['typeAndVersion'], + }), + ) + await assert.rejects( + cct.generateUnsignedTransferOwnership({ poolAddress: POOL, newOwner: TOKEN }), + (err: unknown) => err instanceof CCTContractTypeInvalidError, + ) + }) + }) + + describe('getTokenPoolState', () => { + it('reads through the wrapped chain', async () => { + const probed: string[] = [] + const cct = EVMTokenManager.fromChain( + stubChain({ + typeAndVersion: ((address: string) => { + probed.push(address) + return Promise.resolve(['BurnMintTokenPool', '2.0.0', 'BurnMintTokenPool 2.0.0']) + }) as unknown as EVMChain['typeAndVersion'], + }), + ) + + // the pool getters themselves need a real provider, so this rejects after the probe + await assert.rejects(cct.getTokenPoolState({ poolAddress: POOL })) + assert.deepEqual(probed, [POOL], 'probes the requested pool on the wrapped chain') + }) + + it('rejects an invalid pool address before any RPC, tagged with the operation', async () => { + let probed = false + const cct = EVMTokenManager.fromChain( + stubChain({ + typeAndVersion: (() => { + probed = true + return Promise.resolve(['BurnMintTokenPool', '2.0.0', 'BurnMintTokenPool 2.0.0']) + }) as unknown as EVMChain['typeAndVersion'], + }), + ) + + await assert.rejects( + () => cct.getTokenPoolState({ poolAddress: 'not-an-address' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'getTokenPoolState' && + err.context.param === 'poolAddress', + ) + assert.equal(probed, false, 'validation fails before the typeAndVersion probe') + }) + }) + describe('generateUnsignedAcceptAdmin', () => { + it('encodes acceptAdminRole(token) to the discovered TAR when sender is pending', async () => { + const cct = EVMTokenManager.fromChain( + stubChain({ provider: acceptAdminProvider(TOKEN) as never }), + ) + const unsigned = await cct.generateUnsignedAcceptAdmin({ + tokenAddress: TOKEN, + address: ROUTER, + sender: TOKEN, + }) + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + + const tx = unsigned.transactions[0]! + assert.equal(tx.to, TAR) + assert.equal(tx.from, TOKEN) + assert.ok( + tx.data!.startsWith(ACCEPT_ADMIN_SELECTOR), + 'data starts with acceptAdminRole selector', + ) + assert.equal(tx.data, EXPECTED_ACCEPT_ADMIN) + }) + + it('rejects an invalid address before any RPC, tagged with the operation', async () => { + let called = false + const cct = EVMTokenManager.fromChain( + stubChain({ + getTokenAdminRegistryFor: () => { + called = true + return Promise.resolve(TAR) + }, + }), + ) + await assert.rejects( + () => + cct.generateUnsignedAcceptAdmin({ + tokenAddress: 'not-an-address', + address: ROUTER, + sender: TOKEN, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'acceptAdmin' && + err.context.param === 'tokenAddress', + ) + assert.equal(called, false, 'validation fails before TAR discovery') + }) + + it('rejects when sender is not the pending administrator', async () => { + const cct = EVMTokenManager.fromChain( + stubChain({ provider: acceptAdminProvider(POOL) as never }), + ) + await assert.rejects( + cct.generateUnsignedAcceptAdmin({ + tokenAddress: TOKEN, + address: ROUTER, + sender: TOKEN, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'acceptAdmin' && + err.context.param === 'sender', + ) + }) + }) + + describe('acceptAdmin', () => { + it('signs and submits, resolving to the confirmed tx hash', async () => { + const cct = EVMTokenManager.fromChain( + stubChain({ provider: acceptAdminProvider(TOKEN) as never }), + ) + const result = await cct.acceptAdmin({ + tokenAddress: TOKEN, + address: ROUTER, + sender: TOKEN, + wallet: fakeSigner(), + }) + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-signer wallet', async () => { + const cct = EVMTokenManager.fromChain( + stubChain({ provider: acceptAdminProvider(TOKEN) as never }), + ) + await assert.rejects( + () => + cct.acceptAdmin({ + tokenAddress: TOKEN, + address: ROUTER, + sender: TOKEN, + wallet: {}, + }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + + it('rejects a sender that does not match the executing wallet', async () => { + // fakeSigner().getAddress() resolves to TOKEN; a `sender` other than TOKEN must be + // rejected rather than silently accepted and broadcast from the mismatched wallet. + const cct = EVMTokenManager.fromChain( + stubChain({ provider: acceptAdminProvider(TOKEN) as never }), + ) + await assert.rejects( + () => + cct.acceptAdmin({ + tokenAddress: TOKEN, + address: ROUTER, + sender: POOL, + wallet: fakeSigner(), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'acceptAdmin' && + err.context.param === 'sender', + ) + }) + }) + describe('getTokenAdminRegistry', () => { + const GET_TOKEN_CONFIG_IFACE = new Interface([ + 'function getTokenConfig(address token) view returns (tuple(address administrator, address pendingAdministrator, address tokenPool))', + ]) + const ADMINISTRATOR = '0x' + '77'.repeat(20) + + /** + * Chain stub whose provider answers `getTokenConfig` with `administrator`/zeroed others — + * but only for a call to `TAR` decoding to `TOKEN`. Mirrors the target/argument assertions in + * `token-admin-registry/operations/get-token-admin-registry.test.ts`'s `stubChain`: matching + * on the selector alone can't tell a correct read from one with the call target or decoded + * token swapped, since both would still reach this branch and get `encoded` back. + */ + function stubTarChain(administrator: string) { + const selector = GET_TOKEN_CONFIG_IFACE.getFunction('getTokenConfig')!.selector + const encoded = GET_TOKEN_CONFIG_IFACE.encodeFunctionResult('getTokenConfig', [ + [administrator, ZeroAddress, ZeroAddress], + ]) + return stubChain({ + provider: { + call: async ({ to, data }: { to?: string; data: string }) => { + if (data.slice(0, 10) !== selector) return '0x' + assert.equal(to, TAR, 'calls the resolved TAR, not `address`') + const [token] = GET_TOKEN_CONFIG_IFACE.decodeFunctionData('getTokenConfig', data) + assert.equal(token, TOKEN, 'reads the config for `tokenAddress`') + return encoded + }, + } as never, + }) + } + + it('reads through the wrapped chain, resolving the TAR from `address`', async () => { + const config = await EVMTokenManager.fromChain( + stubTarChain(ADMINISTRATOR), + ).getTokenAdminRegistry({ + address: ROUTER, + tokenAddress: TOKEN, + }) + assert.deepEqual(config, { administrator: ADMINISTRATOR }) + }) + + it('reports a zero administrator rather than throwing', async () => { + const config = await EVMTokenManager.fromChain( + stubTarChain(ZeroAddress), + ).getTokenAdminRegistry({ address: ROUTER, tokenAddress: TOKEN }) + assert.equal(config.administrator, ZeroAddress) + }) + + it('rejects an invalid token address before any RPC, tagged with the operation', async () => { + let called = false + const cct = EVMTokenManager.fromChain( + stubChain({ + getTokenAdminRegistryFor: () => { + called = true + return Promise.resolve(TAR) + }, + }), + ) + await assert.rejects( + () => cct.getTokenAdminRegistry({ address: ROUTER, tokenAddress: 'not-an-address' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'getTokenAdminRegistry' && + err.context.param === 'tokenAddress', + ) + assert.equal(called, false, 'validation fails before TAR discovery') + }) + }) + describe('getSupportedTokens', () => { + it('resolves the TAR and lists its configured tokens', async () => { + const tokens = [TOKEN, POOL] + let seenOpts: { page?: number } | undefined + const cct = EVMTokenManager.fromChain( + stubChain({ + getSupportedTokens: async (registry: string, opts?: { page?: number }) => { + assert.equal(registry, TAR) + seenOpts = opts + return tokens + }, + }), + ) + + const result = await cct.getSupportedTokens({ address: ROUTER }) + assert.deepEqual(result, tokens) + assert.deepEqual(seenOpts, { page: undefined }) + }) + + it('forwards `page` to the wrapped chain', async () => { + let seenPage: number | undefined + const cct = EVMTokenManager.fromChain( + stubChain({ + getSupportedTokens: async (_registry: string, opts?: { page?: number }) => { + seenPage = opts?.page + return [] + }, + }), + ) + + await cct.getSupportedTokens({ address: ROUTER, page: 25 }) + assert.equal(seenPage, 25) + }) + + it('rejects an invalid address before any RPC, tagged with the operation', async () => { + let called = false + const cct = EVMTokenManager.fromChain( + stubChain({ + getTokenAdminRegistryFor: () => { + called = true + return Promise.resolve(TAR) + }, + }), + ) + await assert.rejects( + () => cct.getSupportedTokens({ address: 'not-an-address' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'getSupportedTokens' && + err.context.param === 'address', + ) + assert.equal(called, false, 'validation fails before TAR discovery') + }) + }) + + describe('mint/burn role management', () => { + const ROLE_TOKEN_OWNER = '0x' + '99'.repeat(20) + const ROLE_ACCOUNT = '0x' + 'aa'.repeat(20) + /** Fresh Interface — the manager's own cached one must not be what these assertions compare to. */ + const ROLES = new Interface([ + 'function grantMintAndBurnRoles(address burnAndMinter)', + 'function grantMintRole(address minter)', + 'function grantBurnRole(address burner)', + 'function revokeMintRole(address minter)', + 'function revokeBurnRole(address burner)', + 'function owner() view returns (address)', + 'function isMinter(address minter) view returns (bool)', + 'function isBurner(address burner) view returns (bool)', + ]) + + /** + * Chain stub for a v1.6.2 `FactoryBurnMintERC20` owned by `ROLE_TOKEN_OWNER`, on which + * `ROLE_ACCOUNT` holds `roles` — enough for both the owner gate and the role-state pre-flight. + */ + function roleChain(roles: { isMinter?: boolean; isBurner?: boolean } = {}) { + const results: Record = { + owner: [ROLE_TOKEN_OWNER], + isMinter: [roles.isMinter ?? false], + isBurner: [roles.isBurner ?? false], + } + return stubChain({ + provider: { + call: ({ data }: { data: string }) => { + const fn = ROLES.getFunction(data.slice(0, 10))!.name + return Promise.resolve(ROLES.encodeFunctionResult(fn, results[fn])) + }, + } as never, + }) + } + + /** + * One case per wired op: the two manager methods, the account parameter, and the role state + * that makes its call a real change. The methods are named explicitly rather than indexed by + * string, so a renamed or unwired method is a compile error here. + */ + const CASES = [ + { + fn: 'grantMintAndBurnRoles', + param: 'burnAndMinter', + roles: {}, + generate: (cct: EVMTokenManager, o: object) => + cct.generateUnsignedGrantMintAndBurnRoles(o as never), + submit: (cct: EVMTokenManager, o: object) => cct.grantMintAndBurnRoles(o as never), + }, + { + fn: 'grantMintRole', + param: 'minter', + roles: {}, + generate: (cct: EVMTokenManager, o: object) => + cct.generateUnsignedGrantMintRole(o as never), + submit: (cct: EVMTokenManager, o: object) => cct.grantMintRole(o as never), + }, + { + fn: 'grantBurnRole', + param: 'burner', + roles: {}, + generate: (cct: EVMTokenManager, o: object) => + cct.generateUnsignedGrantBurnRole(o as never), + submit: (cct: EVMTokenManager, o: object) => cct.grantBurnRole(o as never), + }, + { + fn: 'revokeMintRole', + param: 'minter', + roles: { isMinter: true }, + generate: (cct: EVMTokenManager, o: object) => + cct.generateUnsignedRevokeMintRole(o as never), + submit: (cct: EVMTokenManager, o: object) => cct.revokeMintRole(o as never), + }, + { + fn: 'revokeBurnRole', + param: 'burner', + roles: { isBurner: true }, + generate: (cct: EVMTokenManager, o: object) => + cct.generateUnsignedRevokeBurnRole(o as never), + submit: (cct: EVMTokenManager, o: object) => cct.revokeBurnRole(o as never), + }, + ] as const + + for (const { fn, param, roles, generate, submit } of CASES) { + const expected = ROLES.encodeFunctionData(fn, [ROLE_ACCOUNT]) + + it(`generateUnsigned* encodes ${fn}(address) to the token`, async () => { + const cct = EVMTokenManager.fromChain(roleChain(roles)) + const unsigned = await generate(cct, { + tokenAddress: TOKEN, + [param]: ROLE_ACCOUNT, + sender: ROLE_TOKEN_OWNER, + }) + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + const tx = unsigned.transactions[0]! + assert.equal(tx.to, TOKEN) + assert.equal(tx.from, ROLE_TOKEN_OWNER) + assert.equal(tx.data, expected) + }) + + it(`${fn} submits as the token owner`, async () => { + const cct = EVMTokenManager.fromChain(roleChain(roles)) + const { hash } = await submit(cct, { + tokenAddress: TOKEN, + [param]: ROLE_ACCOUNT, + wallet: fakeSigner(ROLE_TOKEN_OWNER), + }) + assert.equal(hash, HASH) + }) + + it(`${fn} rejects a sender that does not own the token`, async () => { + const cct = EVMTokenManager.fromChain(roleChain(roles)) + await assert.rejects( + () => generate(cct, { tokenAddress: TOKEN, [param]: ROLE_ACCOUNT, sender: ADMIN }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + } + }) + + describe('mint and role reads', () => { + const MINTER = '0x' + '99'.repeat(20) + const RECIPIENT = '0x' + 'aa'.repeat(20) + const AMOUNT = 1_000000000000000000n + /** Fresh Interface — the manager's own cached one must not be what these assertions compare to. */ + const TOKEN_FNS = new Interface([ + 'function mint(address account, uint256 amount)', + 'function isMinter(address minter) view returns (bool)', + 'function isBurner(address burner) view returns (bool)', + 'function getMinters() view returns (address[])', + 'function getBurners() view returns (address[])', + ]) + + /** Chain stub for a BurnMintERC677 token on which `MINTER` holds the mint role. */ + function tokenChain(isMinter = true) { + const results: Record = { + isMinter: [isMinter], + isBurner: [isMinter], + getMinters: [[MINTER]], + getBurners: [[POOL]], + } + return stubChain({ + provider: { + call: ({ data }: { data: string }) => { + const fn = TOKEN_FNS.getFunction(data.slice(0, 10))!.name + return Promise.resolve(TOKEN_FNS.encodeFunctionResult(fn, results[fn])) + }, + } as never, + }) + } + + it('generateUnsignedMint encodes mint(account, amount) to the token', async () => { + const cct = EVMTokenManager.fromChain(tokenChain()) + const unsigned = await cct.generateUnsignedMint({ + tokenAddress: TOKEN, + account: RECIPIENT, + amount: AMOUNT, + sender: MINTER, + }) + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + const tx = unsigned.transactions[0]! + assert.equal(tx.to, TOKEN) + assert.equal(tx.from, MINTER) + assert.equal(tx.data, TOKEN_FNS.encodeFunctionData('mint', [RECIPIENT, AMOUNT])) + }) + + it('mint submits as the minting wallet', async () => { + const cct = EVMTokenManager.fromChain(tokenChain()) + const { hash } = await cct.mint({ + tokenAddress: TOKEN, + account: RECIPIENT, + amount: AMOUNT, + wallet: fakeSigner(MINTER), + }) + assert.equal(hash, HASH) + }) + + it('mint rejects a wallet without the mint role', async () => { + const cct = EVMTokenManager.fromChain(tokenChain(false)) + await assert.rejects( + () => + cct.mint({ + tokenAddress: TOKEN, + account: RECIPIENT, + amount: AMOUNT, + wallet: fakeSigner(ADMIN), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('getMinters lists the mint-role holders', async () => { + const cct = EVMTokenManager.fromChain(tokenChain()) + assert.deepEqual(await cct.getMinters({ tokenAddress: TOKEN }), [MINTER]) + }) + + it('getBurners lists the burn-role holders', async () => { + const cct = EVMTokenManager.fromChain(tokenChain()) + assert.deepEqual(await cct.getBurners({ tokenAddress: TOKEN }), [POOL]) + }) + + it('isMinter answers the single-address mint-role check', async () => { + assert.equal( + await EVMTokenManager.fromChain(tokenChain()).isMinter({ + tokenAddress: TOKEN, + account: MINTER, + }), + true, + ) + assert.equal( + await EVMTokenManager.fromChain(tokenChain(false)).isMinter({ + tokenAddress: TOKEN, + account: RECIPIENT, + }), + false, + ) + }) + + it('isBurner answers the single-address burn-role check', async () => { + const cct = EVMTokenManager.fromChain(tokenChain()) + assert.equal(await cct.isBurner({ tokenAddress: TOKEN, account: POOL }), true) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/index.ts b/ccip-sdk/src/cct/evm/index.ts new file mode 100644 index 000000000..811e32bef --- /dev/null +++ b/ccip-sdk/src/cct/evm/index.ts @@ -0,0 +1,1768 @@ +/** + * EVM Cross-Chain Token (CCT) admin operations. + * {@link EVMTokenManager} wraps an {@link EVMChain}: build with + * `generateUnsigned` (sender in opts), then `` with `wallet` in opts. + * + * @packageDocumentation + */ + +import type { JsonRpcApiProvider } from 'ethers' + +import type { ChainContext } from '../../chain.ts' +import { EVMChain } from '../../evm/index.ts' +import type { UnsignedEVMTx } from '../../evm/types.ts' +import type { ChainFamily } from '../../networks.ts' +import type { TransactionResult } from '../operation.ts' +import { TokenManager } from '../token-manager.ts' +import { + type AuthorizeLockboxCallersParams, + AuthorizeLockboxCallers, +} from './lockbox/operations/authorize-callers.ts' +import { type DeployLockboxParams, DeployLockbox } from './lockbox/operations/deploy-lockbox.ts' +import type { DeployResult, EVMExecuteParams } from './operation.ts' +import { + type AcceptAdminParams, + AcceptAdmin, +} from './token-admin-registry/operations/accept-admin.ts' +import { + type GetSupportedTokensParams, + type GetSupportedTokensResult, + GetSupportedTokens, +} from './token-admin-registry/operations/get-supported-tokens.ts' +import { + type GetTokenAdminRegistryParams, + type GetTokenAdminRegistryResult, + GetTokenAdminRegistry, +} from './token-admin-registry/operations/get-token-admin-registry.ts' +import { + type RegisterAdminParams, + RegisterAdmin, +} from './token-admin-registry/operations/register-admin.ts' +import { type SetPoolParams, SetPool } from './token-admin-registry/operations/set-pool.ts' +import { + type TransferAdminParams, + TransferAdmin, +} from './token-admin-registry/operations/transfer-admin.ts' +import { type AddRemotePoolParams, AddRemotePool } from './token-pool/operations/add-remote-pool.ts' +import { + type ApplyAllowlistUpdatesParams, + ApplyAllowlistUpdates, +} from './token-pool/operations/apply-allowlist-updates.ts' +import { + type ApplyChainUpdatesParams, + ApplyChainUpdates, +} from './token-pool/operations/apply-chain-updates.ts' +import { + type DeployTokenPoolParams, + DeployTokenPool, +} from './token-pool/operations/deploy-token-pool.ts' +import { + type GetTokenPoolRemotesParams, + type GetTokenPoolRemotesResult, + GetTokenPoolRemotes, +} from './token-pool/operations/get-token-pool-remotes.ts' +import { + type GetTokenPoolStateParams, + type GetTokenPoolStateResult, + GetTokenPoolState, +} from './token-pool/operations/get-token-pool-state.ts' +import { + type RemoveRemotePoolParams, + RemoveRemotePool, +} from './token-pool/operations/remove-remote-pool.ts' +import { + type SetChainRateLimiterConfigsParams, + SetChainRateLimiterConfigs, +} from './token-pool/operations/set-chain-rate-limiter-configs.ts' +import { + type SetDynamicConfigParams, + SetDynamicConfig, +} from './token-pool/operations/set-dynamic-config.ts' +import { + type SetRateLimitAdminParams, + SetRateLimitAdmin, +} from './token-pool/operations/set-rate-limit-admin.ts' +import { type SetRemotePoolParams, SetRemotePool } from './token-pool/operations/set-remote-pool.ts' +import { + type TransferOwnershipParams, + TransferOwnership, +} from './token-pool/operations/transfer-ownership.ts' +import { type DeployTokenParams, DeployToken } from './token/operations/deploy-token.ts' +import { + type GetBurnersParams, + type GetBurnersResult, + GetBurners, +} from './token/operations/get-burners.ts' +import { + type GetMintersParams, + type GetMintersResult, + GetMinters, +} from './token/operations/get-minters.ts' +import { type GrantBurnRoleParams, GrantBurnRole } from './token/operations/grant-burn-role.ts' +import { + type GrantMintAndBurnRolesParams, + GrantMintAndBurnRoles, +} from './token/operations/grant-mint-and-burn-roles.ts' +import { type GrantMintRoleParams, GrantMintRole } from './token/operations/grant-mint-role.ts' +import { type IsBurnerParams, type IsBurnerResult, IsBurner } from './token/operations/is-burner.ts' +import { type IsMinterParams, type IsMinterResult, IsMinter } from './token/operations/is-minter.ts' +import { type MintParams, Mint } from './token/operations/mint.ts' +import { type RevokeBurnRoleParams, RevokeBurnRole } from './token/operations/revoke-burn-role.ts' +import { type RevokeMintRoleParams, RevokeMintRole } from './token/operations/revoke-mint-role.ts' + +/** CCT admin operations for EVM chains, delegating each op to an operation class. */ +export class EVMTokenManager extends TokenManager { + readonly chain: EVMChain + // Token operations + readonly #deployToken = new DeployToken() + readonly #mint = new Mint() + readonly #grantMintAndBurnRoles = new GrantMintAndBurnRoles() + readonly #grantMintRole = new GrantMintRole() + readonly #grantBurnRole = new GrantBurnRole() + readonly #revokeMintRole = new RevokeMintRole() + readonly #revokeBurnRole = new RevokeBurnRole() + readonly #getMinters = new GetMinters() + readonly #getBurners = new GetBurners() + readonly #isMinter = new IsMinter() + readonly #isBurner = new IsBurner() + + // Token admin registry operations + readonly #registerAdmin = new RegisterAdmin() + readonly #setPool = new SetPool() + readonly #transferAdmin = new TransferAdmin() + readonly #acceptAdmin = new AcceptAdmin() + readonly #getTokenAdminRegistry = new GetTokenAdminRegistry() + readonly #getSupportedTokens = new GetSupportedTokens() + + // Token pool operations + readonly #deployTokenPool = new DeployTokenPool() + readonly #transferOwnership = new TransferOwnership() + readonly #getTokenPoolState = new GetTokenPoolState() + readonly #getTokenPoolRemotes = new GetTokenPoolRemotes() + readonly #setRemotePool = new SetRemotePool() + readonly #addRemotePool = new AddRemotePool() + readonly #removeRemotePool = new RemoveRemotePool() + readonly #applyChainUpdates = new ApplyChainUpdates() + readonly #applyAllowlistUpdates = new ApplyAllowlistUpdates() + readonly #setChainRateLimiterConfigs = new SetChainRateLimiterConfigs() + readonly #setRateLimitAdmin = new SetRateLimitAdmin() + readonly #setDynamicConfig = new SetDynamicConfig() + + // Lockbox operations + readonly #deployLockbox = new DeployLockbox() + readonly #authorizeLockboxCallers = new AuthorizeLockboxCallers() + + /** Wraps an {@link EVMChain}; prefer the static factory methods. */ + constructor(chain: EVMChain) { + super() + this.chain = chain + } + + /** Wraps an existing {@link EVMChain}. */ + static fromChain(chain: EVMChain): EVMTokenManager { + return new EVMTokenManager(chain) + } + + /** Creates from an ethers provider. */ + static async fromProvider( + provider: JsonRpcApiProvider, + ctx?: ChainContext, + ): Promise { + return new EVMTokenManager(await EVMChain.fromProvider(provider, ctx)) + } + + /** Creates from an RPC URL. */ + static async fromUrl(url: string, ctx?: ChainContext): Promise { + return new EVMTokenManager(await EVMChain.fromUrl(url, ctx)) + } + + /** Provider of the underlying chain. */ + get provider(): JsonRpcApiProvider { + return this.chain.provider + } + + /** + * Builds an unsigned `registerAdmin` tx (for multisig / offline signing): proposes a token's + * administrator in the TokenAdminRegistry via a RegistryModuleOwnerCustom. Two-step by design — + * the proposed administrator must then call {@link acceptAdmin}. + * @remarks The administrator is not a parameter — the module derives it on-chain. `owner`/`ccip-admin` read the token's own `owner()`/`getCCIPAdmin()`, so + * the result is independent of who signs; a wrong signer simply reverts (`CanOnlySelfRegister`). + * + * `access-control-default-admin` behaves differently and warrants care on this offline path: the + * module registers **`msg.sender`** after checking it holds the token's `DEFAULT_ADMIN_ROLE`. + * `sender` here only drives the local pre-flight probe, so if the built tx is ultimately signed + * by a *different* address that also holds that role, the **signer** becomes the token's + * administrator — silently, with no revert to catch it. Confirm the signing key before relaying + * an `access-control-default-admin` registration. {@link registerAdmin} is not exposed to this, + * since it rejects a `sender` that differs from its wallet. + * @throws {@link CCTParamsInvalidError} if any param is invalid, `registryModule` is not a + * registered TAR module, `registrationMethod` needs a v1.6+ module, `sender` doesn't match the + * token's authority for the chosen method, or the token is already registered (or pending + * acceptance) + * @example + * ```typescript + * // build only — sign later (multisig / offline). `sender` must be the token's owner (or + * // CCIP admin / default admin, matching `registrationMethod`). + * const unsigned = await cct.generateUnsignedRegisterAdmin({ + * tokenAddress: '0xToken...', + * registryModule: '0xRegistryModuleOwnerCustom...', // not discoverable on-chain + * address: '0xTokenAdminRegistry...', // the TAR, or a Router/OnRamp/OffRamp/pool to resolve it from + * sender: '0xTokenOwner...', + * }) + * ``` + */ + generateUnsignedRegisterAdmin(opts: RegisterAdminParams): Promise { + return this.#registerAdmin.generate(this.chain, opts) + } + + /** + * Proposes a token's administrator in the TokenAdminRegistry via a RegistryModuleOwnerCustom, + * signing + submitting with `opts.wallet`. Two-step by design — the proposed administrator + * must then call {@link acceptAdmin}. + * @remarks The administrator is not a parameter — see {@link generateUnsignedRegisterAdmin}. `sender` also defaults to `opts.wallet`'s address here + * (unlike the unsigned builder, where it's optional for offline/multisig flows), so the + * token-authority check always runs before this signs and submits. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if any param is invalid, `registryModule` is not a + * registered TAR module, `registrationMethod` needs a v1.6+ module, `sender` doesn't match the + * token's authority for the chosen method, or the token is already registered (or pending + * acceptance) + * @throws {@link CCTTxFailedError} if the tx reverts or fails + * @example + * ```typescript + * // `wallet` must be the token's owner (or CCIP admin / hold DEFAULT_ADMIN_ROLE, matching + * // `registrationMethod`) — enforced automatically since `sender` defaults to its address. + * const { hash } = await cct.registerAdmin({ + * tokenAddress: '0xToken...', + * registryModule: '0xRegistryModuleOwnerCustom...', + * address: '0xTokenAdminRegistry...', + * wallet, + * }) + * ``` + */ + registerAdmin(opts: EVMExecuteParams): Promise { + return this.#registerAdmin.execute(this.chain, opts) + } + + /** + * Builds an unsigned `setPool` tx (for multisig / offline signing). + * A zero/empty `poolAddress` delists the token from the registry. + * @throws {@link CCTParamsInvalidError} if any param is invalid + * @example + * ```typescript + * // build only — sign later (multisig / offline). `sender` must be the token's current admin. + * const unsigned = await cct.generateUnsignedSetPool({ + * tokenAddress: '0xToken...', + * poolAddress: '0xPool...', // pass the zero address to delist the token + * address: '0xTokenAdminRegistry...', // the TAR, or a Router/pool to resolve it from + * sender: '0xTokenAdmin...', + * }) + * ``` + */ + generateUnsignedSetPool(opts: SetPoolParams): Promise { + return this.#setPool.generate(this.chain, opts) + } + + /** + * Registers a pool, signing + submitting with `opts.wallet` (the token admin). + * A zero/empty `poolAddress` delists the token from the registry. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if any param is invalid + * @throws {@link CCTTxFailedError} if the tx reverts or fails + * @example + * ```typescript + * // `wallet` must sign as the token's current administrator + * const { hash } = await cct.setPool({ + * tokenAddress: '0xToken...', + * poolAddress: '0xPool...', // pass the zero address to delist the token + * address: '0xTokenAdminRegistry...', + * wallet, + * }) + * ``` + */ + setPool(opts: EVMExecuteParams): Promise { + return this.#setPool.execute(this.chain, opts) + } + + /** + * Builds an unsigned TokenAdminRegistry `transferAdmin` tx (for multisig / offline signing). + * Two-step by design: `newAdmin` must separately call `acceptAdmin` to complete the + * handoff. This is the registry's ADMIN role — distinct from a pool's Ownable2Step *owner* + * (see {@link transferOwnership}); do not confuse the two. + * @throws {@link CCTParamsInvalidError} if any param is invalid, or if `sender` is not the + * token's current registry administrator (including a not-yet-accepted registration) + * @example + * ```typescript + * // `sender` must be the token's current registry administrator + * const unsigned = await cct.generateUnsignedTransferAdmin({ + * tokenAddress: '0xToken...', + * newAdmin: '0xNewAdmin...', // must separately call acceptAdmin + * address: '0xTokenAdminRegistry...', // the TAR, or a Router/pool to resolve it from + * sender: '0xCurrentAdmin...', + * }) + * ``` + */ + generateUnsignedTransferAdmin(opts: TransferAdminParams): Promise { + return this.#transferAdmin.generate(this.chain, opts) + } + + /** + * Proposes a new TokenAdminRegistry administrator, signing + submitting with `opts.wallet` + * (the current registry admin). Two-step: `newAdmin` must separately call `acceptAdmin`. + * This is the registry's ADMIN role — distinct from a pool's Ownable2Step *owner* + * (see {@link transferOwnership}); do not confuse the two. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if any param is invalid, if the signing wallet is not the + * token's current registry administrator (including a not-yet-accepted registration), or if an + * explicit `opts.sender` does not match the wallet's address + * @throws {@link CCTTxFailedError} if the tx reverts or fails + * @example + * ```typescript + * // `wallet` must sign as the token's current registry administrator; `sender` defaults to its + * // address, so pass it only for offline builds via generateUnsignedTransferAdmin. + * const { hash } = await cct.transferAdmin({ + * tokenAddress: '0xToken...', + * newAdmin: '0xNewAdmin...', + * address: '0xTokenAdminRegistry...', + * wallet, + * }) + * ``` + */ + transferAdmin(opts: EVMExecuteParams): Promise { + return this.#transferAdmin.execute(this.chain, opts) + } + + /** + * Builds an unsigned `acceptAdminRole` tx (for multisig / offline signing). Second half of + * the two-step admin handshake: a registry module's `registerAdmin` (fresh registration) or + * the current admin's `transferAdmin` (hand-off) proposes `opts.sender` as + * `pendingAdministrator`; `acceptAdmin` then confirms it on-chain before encoding, after which + * {@link setPool} becomes callable by the new administrator. + * @throws {@link CCTParamsInvalidError} if any param is invalid, or `sender` is not the + * pending administrator + * @example + * ```typescript + * // `sender` must be the pending administrator proposed by registerAdmin/transferAdmin + * const unsigned = await cct.generateUnsignedAcceptAdmin({ + * tokenAddress: '0xToken...', + * address: '0xTokenAdminRegistry...', // the TAR, or a Router/pool to resolve it from + * sender: '0xPendingAdmin...', + * }) + * ``` + */ + generateUnsignedAcceptAdmin(opts: AcceptAdminParams): Promise { + return this.#acceptAdmin.generate(this.chain, opts) + } + + /** + * Accepts a pending TokenAdminRegistry administrator role, signing + submitting with + * `opts.wallet` (the pending administrator). Completes the `registerAdmin`/`transferAdmin` → + * `acceptAdmin` handshake, after which {@link setPool} becomes callable. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if any param is invalid, or `sender` is not the + * pending administrator + * @throws {@link CCTTxFailedError} if the tx reverts or fails + * @example + * ```typescript + * // `wallet` must sign as the pending administrator + * const { hash } = await cct.acceptAdmin({ + * tokenAddress: '0xToken...', + * address: '0xTokenAdminRegistry...', + * wallet, + * }) + * ``` + */ + acceptAdmin(opts: EVMExecuteParams): Promise { + return this.#acceptAdmin.execute(this.chain, opts) + } + + /** + * Reads a token's TokenAdminRegistry entry: its `administrator`, any `pendingAdministrator`, + * and its registered `tokenPool`. + * @remarks Deliberately diverges from `cct.chain.getRegistryTokenConfig()`, which throws when + * `administrator` is the zero address — exactly the post-`registerAdmin`, pre-`acceptAdmin` + * state. This op reports `{ administrator: ZeroAddress, pendingAdministrator }` faithfully + * instead, so a pending registration is observable; see + * {@link GetTokenAdminRegistry} for the full rationale. `pendingAdministrator` and `tokenPool` + * are still omitted when zero. + * @throws {@link CCTParamsInvalidError} if any param is invalid + * @example + * ```typescript + * const config = await cct.getTokenAdminRegistry({ + * address: '0xTokenAdminRegistry...', // or a Router/OnRamp/OffRamp/pool to resolve it from + * tokenAddress: '0xToken...', + * }) + * if (config.administrator === ZeroAddress) { + * console.log('pending acceptance by', config.pendingAdministrator) + * } + * ``` + */ + getTokenAdminRegistry(opts: GetTokenAdminRegistryParams): Promise { + return this.#getTokenAdminRegistry.query(this.chain, opts) + } + + /** + * Lists every token configured in the TokenAdminRegistry resolved from `address`. + * @remarks The registry paginates via `getAllConfiguredTokens` — `opts.page` sets the batch size per call; omit it to read the + * whole registry in one round trip per 1000 tokens. + * @throws {@link CCTParamsInvalidError} if `address` is not a valid address, or `page` is given + * and is not a positive integer + * @example + * ```typescript + * const tokens = await cct.getSupportedTokens({ address: '0xTokenAdminRegistry...' }) + * ``` + */ + getSupportedTokens(opts: GetSupportedTokensParams): Promise { + return this.#getSupportedTokens.query(this.chain, opts) + } + + /** + * Builds an unsigned pool `transferOwnership` tx (for multisig / offline signing). Probes the + * pool's on-chain `typeAndVersion` to resolve its interface + encoder; the `transferOwnership` + * calldata is stable across pool versions, so the resolved encoding is version/type-independent. + * @throws {@link CCTParamsInvalidError} if any param is invalid + */ + generateUnsignedTransferOwnership(opts: TransferOwnershipParams): Promise { + return this.#transferOwnership.generate(this.chain, opts) + } + + /** + * Proposes a new pool owner (two-step), signing + submitting with `opts.wallet`. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if any param is invalid + * @throws {@link CCTTxFailedError} if the tx reverts or fails + */ + transferOwnership(opts: EVMExecuteParams): Promise { + return this.#transferOwnership.execute(this.chain, opts) + } + + /** + * Builds an unsigned pool rate-limit tx (for multisig / offline signing): sets the inbound and + * outbound limits of one or more already-configured lanes, in a single transaction. Probes the + * pool's on-chain `typeAndVersion` to resolve its interface + encoder. + * @remarks **v1.5.0 pools set one lane per transaction.** v1.5.1/v1.6.1 encode the batch + * `setChainRateLimiterConfigs(uint64[], Config[], Config[])` and v2.0.0 the reshaped + * `setRateLimitConfig(RateLimitConfigArgs[])`, but v1.5.0 ships only the singular + * `setChainRateLimiterConfig(uint64, Config, Config)`. To keep the one-op-one-transaction + * contract every CCT write holds, a v1.5.0 pool therefore accepts only a single-element + * `updates`; a multi-lane batch is rejected with {@link CCTParamsInvalidError} rather than + * fanned out into N transactions. + * + * `fastFinality` is **v2.0.0-only** — the flag does not exist in the earlier ABIs, so setting it + * (to either value) on an older pool is rejected rather than silently dropped. It defaults to + * `false` on v2.0.0. + * + * This op *updates* limits on lanes that already exist; it does not add one. An unconfigured + * selector reverts on-chain (`NonExistentChain`). + * + * The tx must ultimately be signed by the pool `owner` **or** its `rateLimitAdmin` — both are + * reported by {@link getTokenPoolState}. When `opts.sender` is supplied it is pre-flighted + * against *both* roles (two extra `eth_call`s — the pool's `owner()` and whichever getter + * reports `rateLimitAdmin` on that version), so a + * `sender` holding neither fails at build time rather than reverting at signing. Omit `sender` + * to build the calldata without any role read, when the eventual signer is not yet known. + * @throws {@link CCTParamsInvalidError} if any param is invalid: `updates` empty, a repeated + * `remoteChainSelector`, a non-`uint64` selector, a rate above its capacity while enabled, a + * non-zero amount while disabled, `fastFinality` set on a pre-2.0.0 pool, or `sender` given and + * being neither the pool `owner` nor its (set) `rateLimitAdmin`. On a **v1.5.1** pool the + * enabled-bucket bound is stricter still (`0 < rate < capacity`), so a `rate` of `0n` or a + * `rate` equal to `capacity` is also rejected there — v1.6.1 and v2.0.0 allow both. A + * **v1.5.0** pool accepts only a single-element `updates`. + * @example + * ```typescript + * const unsigned = await cct.generateUnsignedSetChainRateLimiterConfigs({ + * poolAddress: '0xPool...', + * updates: [ + * { + * remoteChainSelector: 5009297550715157269n, // ethereum-mainnet + * // amounts are in the local token's smallest unit (18 decimals here) + * outboundRateLimiterConfig: { enabled: true, capacity: 10_000n * 10n ** 18n, rate: 100n * 10n ** 18n }, + * inboundRateLimiterConfig: { enabled: false }, // capacity/rate default to 0n + * }, + * ], + * sender: '0xOwnerOrRateLimitAdmin...', + * }) + * ``` + */ + generateUnsignedSetChainRateLimiterConfigs( + opts: SetChainRateLimiterConfigsParams, + ): Promise { + return this.#setChainRateLimiterConfigs.generate(this.chain, opts) + } + + /** + * Sets the inbound and outbound rate limits of one or more already-configured lanes in a single + * transaction, signing + submitting with `opts.wallet`. + * @remarks Gated on **either** the pool `owner` or its `rateLimitAdmin` — rate limits are the one + * pool write that accepts a delegated role, so this check is a disjunction where + * {@link transferOwnership}'s is owner-only. Both roles are reported by + * {@link getTokenPoolState}; `rateLimitAdmin` is the zero address when unset, and an unset role + * matches nobody. + * + * Same version rules as {@link generateUnsignedSetChainRateLimiterConfigs}: **v1.5.0 pools set + * one lane per transaction**, and `fastFinality` is v2.0.0-only. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if any param is invalid, or if `sender` is given and is + * not the wallet's address, or the signer is neither the pool `owner` nor its (set) + * `rateLimitAdmin`. On a **v1.5.1** pool an enabled rate limiter must additionally satisfy + * `0 < rate < capacity`, so a `rate` of `0n` or a `rate` equal to `capacity` is rejected there — + * v1.6.1 and v2.0.0 allow both. A **v1.5.0** pool accepts only a single-element `updates`. + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCTTxNotConfirmedError} if it is not confirmed in time + * @example + * ```typescript + * const { hash } = await cct.setChainRateLimiterConfigs({ + * poolAddress: '0xPool...', + * updates: [ + * { + * remoteChainSelector: 16015286601757825753n, // ethereum-testnet-sepolia + * outboundRateLimiterConfig: { enabled: true, capacity: 1_000n * 10n ** 18n, rate: 10n * 10n ** 18n }, + * inboundRateLimiterConfig: { enabled: true, capacity: 1_000n * 10n ** 18n, rate: 10n * 10n ** 18n }, + * // fastFinality: true, // v2.0.0 pools only — targets the fast-finality buckets + * }, + * ], + * wallet, // the pool owner or its rateLimitAdmin + * }) + * ``` + */ + setChainRateLimiterConfigs( + opts: EVMExecuteParams, + ): Promise { + return this.#setChainRateLimiterConfigs.execute(this.chain, opts) + } + + /** + * Builds an unsigned pool `setRateLimitAdmin` tx (for multisig / offline signing): assigns the + * role allowed to change the pool's rate limits alongside the owner. Probes the pool's on-chain + * `typeAndVersion` to resolve its interface + encoder. + * @remarks Owner-only, unlike the rate-limit *config* writes the pool also accepts from the + * current `rateLimitAdmin` — this call assigns the role itself, so admitting the incumbent + * admin would let it reassign or entrench its own privilege. When `sender` is supplied it is + * checked against the pool's `owner()` before any calldata is built; omit it and no owner read + * is made (nothing to compare against). + * + * A zero `newRateLimitAdmin` is accepted and clears the delegation, leaving the owner as the + * only account that can change rate limits. + * @throws {@link CCTOperationUnsupportedError} on a **v2.0.0** pool — 2.0.0 removed the + * standalone `setRateLimitAdmin(address)` selector and folded the role into a three-field + * dynamic config; use {@link generateUnsignedSetDynamicConfig} / {@link setDynamicConfig} there + * @throws {@link CCTParamsInvalidError} if any param is invalid, `poolAddress` is the zero + * address, or `sender` is given and is not the pool owner + * @throws {@link CCTContractVersionUnsupportedError} if the pool reports an unknown version + * @example + * ```typescript + * // build only — sign later (multisig / offline). `sender` must be the pool owner. + * const unsigned = await cct.generateUnsignedSetRateLimitAdmin({ + * poolAddress: '0xPool...', + * newRateLimitAdmin: '0xOpsMultisig...', + * sender: '0xOwner...', + * }) + * ``` + */ + generateUnsignedSetRateLimitAdmin(opts: SetRateLimitAdminParams): Promise { + return this.#setRateLimitAdmin.generate(this.chain, opts) + } + + /** + * Assigns the pool's rate-limit admin role, signing + submitting with `opts.wallet`. `sender` + * defaults to the wallet's address and must equal it — the wallet must be the pool owner. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTOperationUnsupportedError} on a v2.0.0 pool — use {@link setDynamicConfig} + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not + * the wallet's address, or the wallet is not the pool owner + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCTTxNotConfirmedError} if it is not confirmed in time + * @example + * ```typescript + * const { hash } = await cct.setRateLimitAdmin({ + * poolAddress: '0xPool...', + * newRateLimitAdmin: '0xOpsMultisig...', + * wallet, + * }) + * ``` + */ + setRateLimitAdmin(opts: EVMExecuteParams): Promise { + return this.#setRateLimitAdmin.execute(this.chain, opts) + } + + /** + * Builds an unsigned pool `setDynamicConfig` tx (for multisig / offline signing): replaces a + * **v2.0.0** pool's whole dynamic config — the `router` it accepts ramp calls from, plus the + * `rateLimitAdmin` and `feeAdmin` delegate roles. + * @remarks This is where the pre-2.0.0 `setRouter` / `setRateLimitAdmin` setters went: 2.0.0 + * removed them and writes all three fields together. Consequently **all three params are + * required** — this op deliberately does *not* read `getDynamicConfig()` to fill in what the + * caller omitted. The calldata has to be deterministic at build time: a multisig or cold wallet + * may sign it days later, and a hidden read would bake a value that has since moved on-chain, + * silently reverting an unrelated config change made in the interim. + * + * Read the current triple with {@link getTokenPoolState} and pass it back explicitly, so what + * is signed is exactly what was reviewed. This is also the migration path off + * {@link setRateLimitAdmin} for a 2.0.0 pool. + * + * Owner-only, for the same escalation reason as {@link generateUnsignedSetRateLimitAdmin}. + * Zero `rateLimitAdmin` / `feeAdmin` clear those delegations; `router` must be non-zero, since + * a zero router detaches the pool from CCIP rather than clearing a privilege. + * @throws {@link CCTOperationUnsupportedError} on a pre-v2.0.0 pool, which has no + * `setDynamicConfig` — use {@link generateUnsignedSetRateLimitAdmin} there + * @throws {@link CCTParamsInvalidError} if any param is invalid, `poolAddress` or `router` is + * the zero address, or `sender` is given and is not the pool owner + * @throws {@link CCTContractVersionUnsupportedError} if the pool reports an unknown version + * @example + * ```typescript + * // build only — sign later (multisig / offline). `sender` must be the pool owner. + * const unsigned = await cct.generateUnsignedSetDynamicConfig({ + * poolAddress: '0xPool...', + * router: '0xRouter...', + * rateLimitAdmin: '0xOpsMultisig...', + * feeAdmin: '0xFeeMultisig...', + * sender: '0xOwner...', + * }) + * ``` + */ + generateUnsignedSetDynamicConfig(opts: SetDynamicConfigParams): Promise { + return this.#setDynamicConfig.generate(this.chain, opts) + } + + /** + * Replaces a v2.0.0 pool's dynamic config, signing + submitting with `opts.wallet`. `sender` + * defaults to the wallet's address and must equal it — the wallet must be the pool owner. + * @remarks Writes all three fields in one call, so **all three params are required**: read the + * current triple with {@link getTokenPoolState} and pass back whatever you are not changing, as + * below. A missing field is a validation error, never "leave that one alone" — nothing is + * backfilled from `getDynamicConfig()`; see {@link generateUnsignedSetDynamicConfig} for why. + * On a 2.0.0 pool this replaces {@link setRateLimitAdmin}. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTOperationUnsupportedError} on a pre-v2.0.0 pool — use {@link setRateLimitAdmin} + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not + * the wallet's address, or the wallet is not the pool owner + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCTTxNotConfirmedError} if it is not confirmed in time + * @example + * ```typescript + * // change only rateLimitAdmin: read the current config and pass the rest back unchanged + * const state = await cct.getTokenPoolState({ poolAddress: '0xPool...' }) + * if (state.version !== '2.0.0') throw new Error('pre-2.0.0 pool: use setRateLimitAdmin') + * const { hash } = await cct.setDynamicConfig({ + * poolAddress: '0xPool...', + * router: state.router, + * rateLimitAdmin: '0xOpsMultisig...', + * feeAdmin: state.feeAdmin, + * wallet, + * }) + * ``` + */ + setDynamicConfig(opts: EVMExecuteParams): Promise { + return this.#setDynamicConfig.execute(this.chain, opts) + } + + /** + * Builds an unsigned `CrossChainToken` (v2.0.0) deployment tx (for multisig / offline + * signing). The deployed address is only known once mined, so it is NOT returned here — + * use {@link deployToken} to deploy and receive `{ hash, contractAddress, verification }`. + * @remarks Same post-deploy roles caveat as {@link deployToken} — the pool needs + * `grantMintAndBurnRoles` before it can bridge. + * @throws {@link CCTParamsInvalidError} if any param is invalid + * @example + * ```typescript + * const unsigned = await cct.generateUnsignedDeployToken({ + * name: 'My Token', + * symbol: 'MTK', + * decimals: 18, + * maxSupply: 0n, // 0 = unlimited + * owner: '0xOwner...', // CrossChainToken v2.0.0; ccipAdmin/burnMintRoleAdmin default to owner + * sender: '0xDeployer...', + * }) + * ``` + */ + generateUnsignedDeployToken(opts: DeployTokenParams): Promise { + return this.#deployToken.generate(this.chain, opts) + } + + /** + * Deploys a `CrossChainToken` (v2.0.0), signing + submitting with `opts.wallet`; resolves + * to the tx hash, the newly deployed token address, and a `verification` + * ({@link ExplorerVerificationInput}) for verifying the source on a block explorer. + * @remarks Mint/burn are role-gated (`MINTER_ROLE`/`BURNER_ROLE`); the token grants neither + * to any pool at deploy. `preMint` mints initial supply to `preMintRecipient`, but before a + * pool can bridge, `burnMintRoleAdmin` must `grantMintAndBurnRoles(pool)`. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if any param is invalid + * @throws {@link CCTTxFailedError} if the tx reverts, fails, or mines without an address + * @example + * ```typescript + * const { hash, contractAddress, verification } = await cct.deployToken({ + * name: 'My Token', + * symbol: 'MTK', + * decimals: 18, + * maxSupply: 0n, + * owner: '0xOwner...', + * wallet, + * }) + * ``` + */ + deployToken(opts: EVMExecuteParams): Promise { + return this.#deployToken.execute(this.chain, opts) + } + + /** + * Builds an unsigned `grantMintAndBurnRoles` tx (for multisig / offline signing): grants a + * BurnMintERC677 token's mint **and** burn roles to one account, in a single transaction. This + * is the call that lets a freshly deployed burn/mint pool bridge the token. + * @remarks v1.5.1 / v1.6.2 tokens only — v2.0.0's `CrossChainToken` gates mint/burn through + * AccessControl, which ships separately. Rejected only when `burnAndMinter` already holds + * *both* roles; holding just one still builds, since this call is what completes the pair. + * @remarks {@link deployToken} deploys v2.0.0, so it is not a source of a token these ops + * accept: a v1.5.1 / v1.6.2 `FactoryBurnMintERC20` comes from the CCIP token factory or your + * own deployment, outside this SDK. + * @see {@link deployTokenPool} — the primary use case is granting these roles to a freshly + * deployed pool + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not + * the token owner, or `burnAndMinter` already holds both roles + * @example + * ```typescript + * // build only — sign later (multisig / offline). `sender` must be the token owner. + * const unsigned = await cct.generateUnsignedGrantMintAndBurnRoles({ + * tokenAddress: '0xToken...', + * burnAndMinter: '0xPool...', // the token's burn/mint pool + * sender: '0xTokenOwner...', + * }) + * ``` + */ + generateUnsignedGrantMintAndBurnRoles(opts: GrantMintAndBurnRolesParams): Promise { + return this.#grantMintAndBurnRoles.generate(this.chain, opts) + } + + /** + * Grants a BurnMintERC677 token's mint and burn roles to one account, signing + submitting with + * `opts.wallet` (the token owner). + * @remarks See {@link generateUnsignedGrantMintAndBurnRoles} for the version and redundancy + * rules. `sender` defaults to the wallet's address, so the owner gate always runs before this + * submits. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not + * the wallet's address, the wallet is not the token owner, or `burnAndMinter` already holds + * both roles + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCTTxNotConfirmedError} if it is not confirmed in time + * @example + * ```typescript + * const { hash } = await cct.grantMintAndBurnRoles({ + * tokenAddress: '0xToken...', + * burnAndMinter: '0xPool...', + * wallet, // must be the token owner + * }) + * ``` + */ + grantMintAndBurnRoles( + opts: EVMExecuteParams, + ): Promise { + return this.#grantMintAndBurnRoles.execute(this.chain, opts) + } + + /** + * Builds an unsigned `grantMintRole` tx (for multisig / offline signing): grants a + * BurnMintERC677 token's mint role to one account. Pair it with + * {@link generateUnsignedGrantBurnRole}, or use + * {@link generateUnsignedGrantMintAndBurnRoles} to grant both in one transaction. + * @remarks v1.5.1 / v1.6.2 tokens only; a redundant grant is rejected, since the chain would + * mine it as a silent no-op rather than revert. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not + * the token owner, or `minter` already holds the mint role + * @example + * ```typescript + * const unsigned = await cct.generateUnsignedGrantMintRole({ + * tokenAddress: '0xToken...', + * minter: '0xMinter...', + * sender: '0xTokenOwner...', + * }) + * ``` + * @see {@link deployTokenPool} — the primary use case is granting this role to a freshly + * deployed pool + */ + generateUnsignedGrantMintRole(opts: GrantMintRoleParams): Promise { + return this.#grantMintRole.generate(this.chain, opts) + } + + /** + * Grants a BurnMintERC677 token's mint role to one account, signing + submitting with + * `opts.wallet` (the token owner). + * @remarks See {@link generateUnsignedGrantMintRole} for the version and redundancy rules. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not + * the wallet's address, the wallet is not the token owner, or `minter` already holds the role + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCTTxNotConfirmedError} if it is not confirmed in time + * @example + * ```typescript + * const { hash } = await cct.grantMintRole({ + * tokenAddress: '0xToken...', + * minter: '0xMinter...', + * wallet, // must be the token owner + * }) + * ``` + */ + grantMintRole(opts: EVMExecuteParams): Promise { + return this.#grantMintRole.execute(this.chain, opts) + } + + /** + * Builds an unsigned `grantBurnRole` tx (for multisig / offline signing): grants a + * BurnMintERC677 token's burn role to one account. Pair it with + * {@link generateUnsignedGrantMintRole}, or use + * {@link generateUnsignedGrantMintAndBurnRoles} to grant both in one transaction. + * @remarks v1.5.1 / v1.6.2 tokens only; a redundant grant is rejected — see + * {@link generateUnsignedGrantMintRole}. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not + * the token owner, or `burner` already holds the burn role + * @example + * ```typescript + * const unsigned = await cct.generateUnsignedGrantBurnRole({ + * tokenAddress: '0xToken...', + * burner: '0xBurner...', + * sender: '0xTokenOwner...', + * }) + * ``` + * @see {@link deployTokenPool} — the primary use case is granting this role to a freshly + * deployed pool + */ + generateUnsignedGrantBurnRole(opts: GrantBurnRoleParams): Promise { + return this.#grantBurnRole.generate(this.chain, opts) + } + + /** + * Grants a BurnMintERC677 token's burn role to one account, signing + submitting with + * `opts.wallet` (the token owner). + * @remarks See {@link generateUnsignedGrantBurnRole} for the version and redundancy rules. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not + * the wallet's address, the wallet is not the token owner, or `burner` already holds the role + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCTTxNotConfirmedError} if it is not confirmed in time + * @example + * ```typescript + * const { hash } = await cct.grantBurnRole({ + * tokenAddress: '0xToken...', + * burner: '0xBurner...', + * wallet, // must be the token owner + * }) + * ``` + */ + grantBurnRole(opts: EVMExecuteParams): Promise { + return this.#grantBurnRole.execute(this.chain, opts) + } + + /** + * Builds an unsigned `revokeMintRole` tx (for multisig / offline signing): removes a + * BurnMintERC677 token's mint role from one account. + * @remarks v1.5.1 / v1.6.2 tokens only; revoking a role the account does not hold is rejected, + * since the chain would mine it as a silent no-op and tell you nothing. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not + * the token owner, or `minter` does not currently hold the mint role + * @example + * ```typescript + * const unsigned = await cct.generateUnsignedRevokeMintRole({ + * tokenAddress: '0xToken...', + * minter: '0xOldPool...', // must currently hold the role + * sender: '0xTokenOwner...', + * }) + * ``` + * @see {@link deployTokenPool} — the mirror of the grant made to a freshly deployed pool + */ + generateUnsignedRevokeMintRole(opts: RevokeMintRoleParams): Promise { + return this.#revokeMintRole.generate(this.chain, opts) + } + + /** + * Removes a BurnMintERC677 token's mint role from one account, signing + submitting with + * `opts.wallet` (the token owner). + * @remarks See {@link generateUnsignedRevokeMintRole} for the version and role-state rules. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not + * the wallet's address, the wallet is not the token owner, or `minter` does not hold the role + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCTTxNotConfirmedError} if it is not confirmed in time + * @example + * ```typescript + * const { hash } = await cct.revokeMintRole({ + * tokenAddress: '0xToken...', + * minter: '0xOldPool...', + * wallet, // must be the token owner + * }) + * ``` + */ + revokeMintRole(opts: EVMExecuteParams): Promise { + return this.#revokeMintRole.execute(this.chain, opts) + } + + /** + * Builds an unsigned `revokeBurnRole` tx (for multisig / offline signing): removes a + * BurnMintERC677 token's burn role from one account. + * @remarks v1.5.1 / v1.6.2 tokens only; revoking a role the account does not hold is rejected — + * see {@link generateUnsignedRevokeMintRole}. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not + * the token owner, or `burner` does not currently hold the burn role + * @example + * ```typescript + * const unsigned = await cct.generateUnsignedRevokeBurnRole({ + * tokenAddress: '0xToken...', + * burner: '0xOldPool...', // must currently hold the role + * sender: '0xTokenOwner...', + * }) + * ``` + * @see {@link deployTokenPool} — the mirror of the grant made to a freshly deployed pool + */ + generateUnsignedRevokeBurnRole(opts: RevokeBurnRoleParams): Promise { + return this.#revokeBurnRole.generate(this.chain, opts) + } + + /** + * Removes a BurnMintERC677 token's burn role from one account, signing + submitting with + * `opts.wallet` (the token owner). + * @remarks See {@link generateUnsignedRevokeBurnRole} for the version and role-state rules. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not + * the wallet's address, the wallet is not the token owner, or `burner` does not hold the role + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCTTxNotConfirmedError} if it is not confirmed in time + * @example + * ```typescript + * const { hash } = await cct.revokeBurnRole({ + * tokenAddress: '0xToken...', + * burner: '0xOldPool...', + * wallet, // must be the token owner + * }) + * ``` + */ + revokeBurnRole(opts: EVMExecuteParams): Promise { + return this.#revokeBurnRole.execute(this.chain, opts) + } + + /** + * Builds an unsigned `mint` tx (for multisig / offline signing): mints new supply of a + * BurnMintERC677 token to `account`. The manual mint — seeding liquidity, topping up test + * supply — not the bridge path, which mints through the pool. + * @remarks v1.5.1 / v1.6.2 tokens only; v2.0.0's `CrossChainToken` gates minting through + * AccessControl, which ships separately. `sender` is checked against the token's + * `isMinter(address)`, **not** its owner: `mint` is `onlyMinter`, and the owner is the role + * admin, who need not hold the role. Grant it first with `grantMintRole`. The full sequence: + * {@link deployToken} → `grantMintRole` → {@link generateUnsignedMint}, checking the grant + * landed with {@link isMinter} (or {@link getMinters} for the whole set). + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @throws {@link CCTParamsInvalidError} if any param is invalid, or `sender` is given and does + * not hold the token's mint role + * @example + * ```typescript + * // build only — sign later (multisig / offline). `sender` must hold the mint role. + * const unsigned = await cct.generateUnsignedMint({ + * tokenAddress: '0xToken...', + * account: '0xRecipient...', + * amount: 1_000_000000000000000000n, // 1000 tokens at 18 decimals + * sender: '0xMinter...', + * }) + * ``` + */ + generateUnsignedMint(opts: MintParams): Promise { + return this.#mint.generate(this.chain, opts) + } + + /** + * Mints new supply of a BurnMintERC677 token to `account`, signing + submitting with + * `opts.wallet` (an address holding the token's mint role). + * @remarks See {@link generateUnsignedMint} for the version and role rules. `sender` defaults + * to the wallet's address, so the mint-role check always runs before this submits. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not + * the wallet's address, or the wallet does not hold the token's mint role + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain — e.g. the mint would + * exceed the token's `maxSupply`, which is not pre-flighted + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCTTxNotConfirmedError} if it is not confirmed in time + * @example + * ```typescript + * const { hash } = await cct.mint({ + * tokenAddress: '0xToken...', + * account: '0xRecipient...', + * amount: 1_000_000000000000000000n, + * wallet, // must hold the mint role + * }) + * ``` + */ + mint(opts: EVMExecuteParams): Promise { + return this.#mint.execute(this.chain, opts) + } + + /** + * Lists every account holding a BurnMintERC677 token's mint role, via `getMinters()`. + * @remarks Informational, for audit and UX. To check *one* address, use {@link isMinter} — one + * call instead of an unbounded set plus a client-side scan. + * @remarks v1.5.1 / v1.6.2 tokens only: v2.0.0's `CrossChainToken` uses AccessControl, which + * does not enumerate role members, so there is no equivalent read. + * @throws {@link CCTParamsInvalidError} if `tokenAddress` is not a valid, non-zero address + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @example + * ```typescript + * const minters = await cct.getMinters({ tokenAddress: '0xToken...' }) + * console.log(minters) // ['0xPool...', '0xOpsKey...'] + * ``` + */ + getMinters(opts: GetMintersParams): Promise { + return this.#getMinters.query(this.chain, opts) + } + + /** + * Lists every account holding a BurnMintERC677 token's burn role, via `getBurners()`. + * @remarks Same shape and caveats as {@link getMinters}; to check one address, use + * {@link isBurner}. + * @throws {@link CCTParamsInvalidError} if `tokenAddress` is not a valid, non-zero address + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @example + * ```typescript + * const burners = await cct.getBurners({ tokenAddress: '0xToken...' }) + * ``` + */ + getBurners(opts: GetBurnersParams): Promise { + return this.#getBurners.query(this.chain, opts) + } + + /** + * Reads whether `account` holds a BurnMintERC677 token's mint role, via `isMinter(address)`. + * @remarks The pre-flight for a {@link mint}: the token's `mint` is `onlyMinter`, and the owner + * is only the role admin, who need not hold the role. Prefer this over scanning + * {@link getMinters} — one call, and it stays a single call as the role set grows. + * @throws {@link CCTParamsInvalidError} if `tokenAddress` or `account` is not a valid, non-zero + * address + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @example + * ```typescript + * if (await cct.isMinter({ tokenAddress: '0xToken...', account: '0xOpsKey...' })) { + * await cct.mint({ tokenAddress: '0xToken...', account: '0xRecipient...', amount, wallet }) + * } + * ``` + */ + isMinter(opts: IsMinterParams): Promise { + return this.#isMinter.query(this.chain, opts) + } + + /** + * Reads whether `account` holds a BurnMintERC677 token's burn role, via `isBurner(address)`. + * @remarks Same shape and caveats as {@link isMinter}; the burn-role counterpart of the set + * read {@link getBurners}. + * @throws {@link CCTParamsInvalidError} if `tokenAddress` or `account` is not a valid, non-zero + * address + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * (a v2.0.0 `CrossChainToken` included, since it gates mint/burn through AccessControl) + * @example + * ```typescript + * const poolCanBurn = await cct.isBurner({ tokenAddress: '0xToken...', account: '0xPool...' }) + * ``` + */ + isBurner(opts: IsBurnerParams): Promise { + return this.#isBurner.query(this.chain, opts) + } + + /** + * Builds an unsigned pool deployment tx (for multisig / offline signing). `type` selects + * the pool contract — a `DeployableTokenPoolType` (`BurnMintTokenPool`, `BurnFromMintTokenPool`, + * `BurnWithFromMintTokenPool`, or `LockReleaseTokenPool`; all v2.0.0). The deployed address is + * only known once mined, so it is NOT returned here — use {@link deployTokenPool} to receive + * `{ hash, contractAddress, verification }`. + * @remarks Same post-deploy setup caveat as {@link deployTokenPool} — a fresh pool must be + * registered, role-granted, and lane-configured before it can bridge. `LockReleaseTokenPool` + * additionally requires a pre-deployed `lockbox` ({@link DeployLockReleaseTokenPoolParams}) + * with the pool authorized on it. The full sequence: {@link deployToken} → {@link deployLockbox} + * → {@link deployTokenPool} (passing the lockbox) → {@link authorizeLockboxCallers} + * (`addedCallers: [pool]`) → {@link setPool} → configure lanes. + * @throws {@link CCTParamsInvalidError} if any param is invalid + * @example + * ```typescript + * const unsigned = await cct.generateUnsignedDeployTokenPool({ + * type: 'BurnMintTokenPool', // burn-* variant; LockReleaseTokenPool additionally requires `lockbox` + * token: '0xToken...', + * localTokenDecimals: 18, + * rmnProxy: '0xRmnProxy...', + * router: '0xRouter...', + * sender: '0xDeployer...', + * }) + * ``` + */ + generateUnsignedDeployTokenPool(opts: DeployTokenPoolParams): Promise { + return this.#deployTokenPool.generate(this.chain, opts) + } + + /** + * Deploys a token pool, signing + submitting with `opts.wallet`; resolves to the tx hash, the + * newly deployed pool address, and a `verification` ({@link ExplorerVerificationInput}) for + * verifying the source on a block explorer. `type` selects the pool contract (a + * `DeployableTokenPoolType`, v2.0.0). + * @remarks Deploying the pool alone doesn't make it usable: register it with {@link setPool}, + * grant it the token's mint/burn roles (`grantMintAndBurnRoles`), and configure its remote + * pools + rate limits before it can bridge. `LockReleaseTokenPool` also needs a pre-deployed + * `lockbox` and the pool authorized on it ({@link DeployLockReleaseTokenPoolParams}). The full + * sequence: {@link deployToken} → {@link deployLockbox} → {@link deployTokenPool} (passing the + * lockbox) → {@link authorizeLockboxCallers} (`addedCallers: [pool]`) → {@link setPool} → + * configure lanes. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if any param is invalid + * @throws {@link CCTTxFailedError} if the tx reverts, fails, or mines without an address + * @example + * ```typescript + * const { hash, contractAddress, verification } = await cct.deployTokenPool({ + * type: 'LockReleaseTokenPool', + * token: '0xToken...', + * localTokenDecimals: 18, + * rmnProxy: '0xRmnProxy...', + * router: '0xRouter...', + * lockbox: '0xLockbox...', // required for LockReleaseTokenPool; must be a non-zero address + * wallet, + * }) + * ``` + */ + deployTokenPool(opts: EVMExecuteParams): Promise { + return this.#deployTokenPool.execute(this.chain, opts) + } + + /** + * Builds an unsigned `ERC20LockBox` (v2.0.0) deployment tx (for multisig / offline signing). + * A lockbox escrows a single `token` for `LockReleaseTokenPool`s. The deployed address is + * only known once mined, so it is NOT returned here — use {@link deployLockbox} to receive + * `{ hash, contractAddress, verification }`. + * @remarks Deploy the lockbox before its pool, then authorize the pool on it with + * {@link authorizeLockboxCallers} before the pool can lock/release. + * @throws {@link CCTParamsInvalidError} if any param is invalid + * @example + * ```typescript + * const unsigned = await cct.generateUnsignedDeployLockbox({ + * token: '0xToken...', // must be non-zero; the same token the LockReleaseTokenPool manages + * sender: '0xDeployer...', + * }) + * ``` + */ + generateUnsignedDeployLockbox(opts: DeployLockboxParams): Promise { + return this.#deployLockbox.generate(this.chain, opts) + } + + /** + * Deploys an `ERC20LockBox` (v2.0.0), signing + submitting with `opts.wallet`; resolves to the + * tx hash, the newly deployed lockbox address, and a `verification` + * ({@link ExplorerVerificationInput}) for verifying the source on a block explorer. + * @remarks Step two of the lock/release flow: {@link deployToken} → {@link deployLockbox} → + * {@link deployTokenPool} (passing this lockbox) → {@link authorizeLockboxCallers} + * (`addedCallers: [pool]`) → {@link setPool} → configure lanes. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if any param is invalid + * @throws {@link CCTTxFailedError} if the tx reverts, fails, or mines without an address + * @example + * ```typescript + * const { hash, contractAddress, verification } = await cct.deployLockbox({ + * token: '0xToken...', + * wallet, + * }) + * ``` + */ + deployLockbox(opts: EVMExecuteParams): Promise { + return this.#deployLockbox.execute(this.chain, opts) + } + + /** + * Builds an unsigned `ERC20LockBox` `applyAuthorizedCallerUpdates` tx (for multisig / offline + * signing) that adds/removes authorized callers. Authorize a `LockReleaseTokenPool` here so it + * can lock/release against the lockbox. + * @throws {@link CCTParamsInvalidError} if any param is invalid, or if no caller is supplied + * @example + * ```typescript + * // `sender` must be the lockbox owner + * const unsigned = await cct.generateUnsignedAuthorizeLockboxCallers({ + * lockbox: '0xLockbox...', + * addedCallers: ['0xPool...'], // the LockReleaseTokenPool to authorize + * sender: '0xLockboxOwner...', + * }) + * ``` + */ + generateUnsignedAuthorizeLockboxCallers( + opts: AuthorizeLockboxCallersParams, + ): Promise { + return this.#authorizeLockboxCallers.generate(this.chain, opts) + } + + /** + * Adds/removes authorized callers on an `ERC20LockBox`, signing + submitting with `opts.wallet` + * (the lockbox owner). Authorize the `LockReleaseTokenPool` before it can lock/release. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if any param is invalid, or if no caller is supplied + * @throws {@link CCTTxFailedError} if the tx reverts or fails + * @example + * ```typescript + * // `wallet` must sign as the lockbox owner + * const { hash } = await cct.authorizeLockboxCallers({ + * lockbox: '0xLockbox...', + * addedCallers: ['0xPool...'], + * wallet, + * }) + * ``` + */ + authorizeLockboxCallers( + opts: EVMExecuteParams, + ): Promise { + return this.#authorizeLockboxCallers.execute(this.chain, opts) + } + + /** + * Reads a pool's admin state, v1.5.0 through v2.0.0: the `owner` every pool write is gated on, + * the `rateLimitAdmin` role, its token/router and configured lanes — plus, on v2.0.0 pools, the + * `feeAdmin` role, the allowed finality window, and a lock/release pool's `lockBox`. + * @remarks The result is a union: `state.version === '2.0.0'` gates the roles and finality + * window that version added, and `state.type === 'LockReleaseTokenPool'` gates its `lockBox` + * (see the example) — a `SiloedLockReleaseTokenPool` reports no `lockBox`, since it escrows per + * remote chain. For a legacy pool's `allowList` / `rebalancer`, proxy/USDC pools, or a v1.5.0 + * `*AndProxy` pool's `previousPool` (it reads here as its base `type`), use + * `cct.chain.getTokenPoolConfig()`, the tolerant transfer-flow read. No pool version exposes a + * pending-owner getter, so a proposed owner is not readable here. + * @remarks The Solana counterpart, `SolanaTokenManager.getTokenPoolState`, returns a different + * shape: its fields nest under `state.config` where these are flat, it spells `token` / + * `tokenDecimals` / `rmnProxy` as `config.mint` / `config.decimals` / `config.rmnRemote`, and its + * `version` is the account-layout number, not this protocol semver. `owner`, `rateLimitAdmin` + * and `router` are named alike on both. + * @throws {@link CCTParamsInvalidError} if `poolAddress` is not a valid address + * @throws {@link CCTContractTypeInvalidError} if the pool is not a supported CCT pool type + * @throws {@link CCTContractVersionUnsupportedError} if the pool's version is not a known one + * @example + * ```typescript + * const state = await cct.getTokenPoolState({ poolAddress: '0xPool...' }) + * // state.owner must sign transferOwnership / lane config; state.rateLimitAdmin may set rate limits + * if (state.version === '2.0.0') { + * console.log(state.feeAdmin, state.finalityDepth) + * if (state.type === 'LockReleaseTokenPool') console.log(state.lockBox) + * } + * ``` + */ + getTokenPoolState(opts: GetTokenPoolStateParams): Promise { + return this.#getTokenPoolState.query(this.chain, opts) + } + + /** + * Reads a pool's remote-lane configuration, v1.5.0 through v2.0.0: for each configured remote + * chain, the `remoteToken`, the `remotePools` authorized to mint/release against it, and the + * inbound/outbound rate-limiter buckets. Keyed by remote network name. + * @remarks Omit `remoteChainSelector` to scan every lane the pool reports through + * `getSupportedChains()`; provide it to read one, which is the cheaper call by far on a pool with + * many lanes. Passing a selector the pool has no config for surfaces as + * {@link CCIPTokenPoolChainConfigNotFoundError} rather than an empty result. + * + * A lane's rate limiter is nullable: `inboundRateLimiterState` / `outboundRateLimiterState` are + * `null` when that direction is unlimited, so check for `null` before reading `.capacity`. + * Amounts are in the *local* token's smallest unit. On v2.0.0 pools each entry additionally + * carries `fastInboundRateLimiterState` / `fastOutboundRateLimiterState`, the separate buckets + * applied to Faster-Than-Finality and safe-finality (FCR) transfers. + * @remarks Every pool-version difference is handled for you — v1.5.0's singular `getRemotePool` + * vs v1.5.1+'s `getRemotePools`, and a `USDCTokenPoolProxy`'s indirection through its underlying + * pools. Reads only; to change a lane use the lane-configuration write ops. + * @remarks The Solana counterpart, `SolanaTokenManager.getTokenPoolRemotes`, returns this same + * {@link TokenPoolRemote} shape, but is addressed differently: it takes the token `mint` plus a + * pool program, where this takes the pool contract address directly. + * @throws {@link CCTParamsInvalidError} if `poolAddress` is not a valid address, or + * `remoteChainSelector` is given and is not a `uint64` + * @throws {@link CCIPTokenPoolChainConfigNotFoundError} if a scanned lane has no remote token + * configured + * @example + * ```typescript + * // every configured lane + * const remotes = await cct.getTokenPoolRemotes({ poolAddress: '0xPool...' }) + * for (const [network, lane] of Object.entries(remotes)) { + * const inbound = lane.inboundRateLimiterState + * console.log(network, lane.remoteToken, lane.remotePools, inbound?.capacity ?? 'unlimited') + * } + * + * // or just one, avoiding a full scan + * const one = await cct.getTokenPoolRemotes({ + * poolAddress: '0xPool...', + * remoteChainSelector: 5009297550715157269n, // ethereum-mainnet + * }) + * ``` + */ + getTokenPoolRemotes(opts: GetTokenPoolRemotesParams): Promise { + return this.#getTokenPoolRemotes.query(this.chain, opts) + } + + /** + * Builds an unsigned pool `setRemotePool` tx (for multisig / offline signing), replacing the + * remote pool a lane accepts. + * @remarks **v1.5.0 pools only.** A v1.5.0 pool holds exactly one remote pool per lane, and this + * call overwrites it. v1.5.1 replaced it with the additive `addRemotePool` / `removeRemotePool` + * pair and dropped `setRemotePool` from the ABI, so a v1.5.1, v1.6.1 or v2.0.0 pool throws + * {@link CCTOperationUnsupportedError} — use {@link generateUnsignedAddRemotePool} / + * {@link generateUnsignedRemoveRemotePool} there. No emulation is attempted: replacing a set of + * unknown size is not one transaction. + * @remarks `remotePoolAddress` is the *remote* chain's pool address as raw `bytes` (`0x` prefix + * optional), not an EVM address — a Solana, Aptos or Sui pool address is 32 bytes. + * @remarks Owner-gated on-chain. When `sender` is given it is checked against the pool's current + * `owner` before any calldata is built; omit it to build for a signer that is not known yet. + * @throws {@link CCTParamsInvalidError} if any param is invalid, or `sender` is given and is not + * the pool owner + * @throws {@link CCTOperationUnsupportedError} if the pool is v1.5.1 or newer + * @throws {@link CCTContractTypeInvalidError} if `poolAddress` is not a supported pool type + * @example + * ```typescript + * const unsigned = await cct.generateUnsignedSetRemotePool({ + * poolAddress: '0xPool...', // a v1.5.0 pool + * remoteChainSelector: 5009297550715157269n, // ethereum-mainnet + * remotePoolAddress: '0xRemotePool...', // hex bytes; 32 bytes for a non-EVM remote + * sender: '0xPoolOwner...', + * }) + * ``` + */ + generateUnsignedSetRemotePool(opts: SetRemotePoolParams): Promise { + return this.#setRemotePool.generate(this.chain, opts) + } + + /** + * Replaces the remote pool a v1.5.0 pool accepts on one lane, signing + submitting with + * `opts.wallet`. See {@link generateUnsignedSetRemotePool} for the version range and the + * `remotePoolAddress` encoding. + * @remarks `sender` defaults to the signing wallet, which must be the pool owner; passing a + * different `sender` is rejected rather than signed — build with + * {@link generateUnsignedSetRemotePool} for externally-signed flows. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if any param is invalid, or `sender` is given and is not + * the wallet's address / the pool owner + * @throws {@link CCTOperationUnsupportedError} if the pool is v1.5.1 or newer + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCTTxNotConfirmedError} if it is not confirmed in time + * @example + * ```typescript + * const { hash } = await cct.setRemotePool({ + * poolAddress: '0xPool...', // a v1.5.0 pool + * remoteChainSelector: 5009297550715157269n, + * remotePoolAddress: '0xRemotePool...', + * wallet, // the pool owner + * }) + * ``` + */ + setRemotePool(opts: EVMExecuteParams): Promise { + return this.#setRemotePool.execute(this.chain, opts) + } + + /** + * Builds an unsigned pool `addRemotePool` tx (for multisig / offline signing), authorizing one + * more remote pool on a lane. + * @remarks **v1.5.1, v1.6.1 and v2.0.0 pools.** From v1.5.1 a lane holds a *set* of remote + * pools, which is what makes a zero-downtime remote-side pool upgrade possible: add the new + * pool, drain the old one, then {@link removeRemotePool}. A v1.5.0 pool has no additive + * primitive and throws {@link CCTOperationUnsupportedError} — it only supports the wholesale + * {@link setRemotePool}. + * @remarks `remotePoolAddress` is the *remote* chain's pool address as raw `bytes` (`0x` prefix + * optional), not an EVM address — a Solana, Aptos or Sui pool address is 32 bytes. + * @remarks Pre-checked against the chain: the lane's currently registered remote pools are read + * (scoped to `remoteChainSelector`, one call) and an address already among them is rejected + * locally instead of reverting on-chain. A lane with no configuration yet counts as having none. + * Owner-gated: a given `sender` is checked against the pool's `owner`. + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not the + * pool owner, or `remotePoolAddress` is already registered on that lane + * @throws {@link CCTOperationUnsupportedError} if the pool is v1.5.0 + * @throws {@link CCTContractTypeInvalidError} if `poolAddress` is not a supported pool type + * @example + * ```typescript + * const unsigned = await cct.generateUnsignedAddRemotePool({ + * poolAddress: '0xPool...', + * remoteChainSelector: 16015286601757825753n, // ethereum-testnet-sepolia + * remotePoolAddress: '0xNewRemotePool...', + * sender: '0xPoolOwner...', + * }) + * ``` + */ + generateUnsignedAddRemotePool(opts: AddRemotePoolParams): Promise { + return this.#addRemotePool.generate(this.chain, opts) + } + + /** + * Authorizes an additional remote pool on one lane of a v1.5.1+ pool, signing + submitting with + * `opts.wallet`. See {@link generateUnsignedAddRemotePool} for the version range, the + * `remotePoolAddress` encoding and the duplicate pre-check. + * @remarks `sender` defaults to the signing wallet, which must be the pool owner; passing a + * different `sender` is rejected rather than signed — build with + * {@link generateUnsignedAddRemotePool} for externally-signed flows. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not the + * wallet's address / the pool owner, or `remotePoolAddress` is already registered on that lane + * @throws {@link CCTOperationUnsupportedError} if the pool is v1.5.0 + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCTTxNotConfirmedError} if it is not confirmed in time + * @example + * ```typescript + * const { hash } = await cct.addRemotePool({ + * poolAddress: '0xPool...', + * remoteChainSelector: 16015286601757825753n, + * remotePoolAddress: '0xNewRemotePool...', + * wallet, // the pool owner + * }) + * ``` + */ + addRemotePool(opts: EVMExecuteParams): Promise { + return this.#addRemotePool.execute(this.chain, opts) + } + + /** + * Builds an unsigned pool `removeRemotePool` tx (for multisig / offline signing), + * de-authorizing one remote pool on a lane. + * @remarks **v1.5.1, v1.6.1 and v2.0.0 pools** — the versions where a lane holds a set of remote + * pools. The last step of a remote-side pool upgrade started with {@link addRemotePool}. A + * v1.5.0 pool has no removal primitive and throws {@link CCTOperationUnsupportedError}; its + * single remote pool can only be overwritten via {@link setRemotePool}. + * @remarks `remotePoolAddress` is the *remote* chain's pool address as raw `bytes` (`0x` prefix + * optional), not an EVM address — a Solana, Aptos or Sui pool address is 32 bytes. + * @remarks Pre-checked against the chain: the lane's registered remote pools are read (scoped to + * `remoteChainSelector`, one call) and an address that is not among them is rejected locally + * instead of reverting on-chain. Removing the lane's last remote pool is allowed — the contract + * decides — but a lane with no configuration at all has nothing to remove and is rejected. + * Owner-gated: a given `sender` is checked against the pool's `owner`. + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not the + * pool owner, or `remotePoolAddress` is not registered on that lane + * @throws {@link CCTOperationUnsupportedError} if the pool is v1.5.0 + * @throws {@link CCTContractTypeInvalidError} if `poolAddress` is not a supported pool type + * @example + * ```typescript + * const unsigned = await cct.generateUnsignedRemoveRemotePool({ + * poolAddress: '0xPool...', + * remoteChainSelector: 16015286601757825753n, + * remotePoolAddress: '0xDrainedRemotePool...', + * sender: '0xPoolOwner...', + * }) + * ``` + */ + generateUnsignedRemoveRemotePool(opts: RemoveRemotePoolParams): Promise { + return this.#removeRemotePool.generate(this.chain, opts) + } + + /** + * De-authorizes a remote pool on one lane of a v1.5.1+ pool, signing + submitting with + * `opts.wallet`. See {@link generateUnsignedRemoveRemotePool} for the version range, the + * `remotePoolAddress` encoding and the membership pre-check. + * @remarks `sender` defaults to the signing wallet, which must be the pool owner; passing a + * different `sender` is rejected rather than signed — build with + * {@link generateUnsignedRemoveRemotePool} for externally-signed flows. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not the + * wallet's address / the pool owner, or `remotePoolAddress` is not registered on that lane + * @throws {@link CCTOperationUnsupportedError} if the pool is v1.5.0 + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCTTxNotConfirmedError} if it is not confirmed in time + * @example + * ```typescript + * const { hash } = await cct.removeRemotePool({ + * poolAddress: '0xPool...', + * remoteChainSelector: 16015286601757825753n, + * remotePoolAddress: '0xDrainedRemotePool...', + * wallet, // the pool owner + * }) + * ``` + */ + removeRemotePool(opts: EVMExecuteParams): Promise { + return this.#removeRemotePool.execute(this.chain, opts) + } + + /** + * Applies the pool's remote-lane configuration, signing + submitting with `opts.wallet`. + * @remarks Same version-discriminated params as + * {@link generateUnsignedApplyChainUpdates} — see there for the v1.5.0 vs v1.5.1 divergence. + * `opts.sender` defaults to the wallet's own address (the only address `onlyOwner` can pass) and + * is rejected if it differs, so the wallet must be the pool owner. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if any param is invalid, `version` does not match the + * pool's own generation, or `sender` is given and is not the wallet address / pool owner. As + * with {@link generateUnsignedApplyChainUpdates}, an enabled rate limiter on a **v1.5.0 or + * v1.5.1** pool must satisfy the stricter `0 < rate < capacity`. + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCTTxNotConfirmedError} if it is not confirmed in time + * @example + * ```typescript + * // `wallet` must sign as the pool owner + * const { hash } = await cct.applyChainUpdates({ + * version: '1.5.1', + * poolAddress: '0xPool...', + * remoteChainSelectorsToRemove: [], + * chainsToAdd: [ + * { + * remoteChainSelector: 16015286601757825753n, + * remoteTokenAddress: '0xRemoteToken...', + * remotePoolAddresses: ['0xRemotePool...'], + * inboundRateLimiterConfig: { enabled: false }, + * outboundRateLimiterConfig: { enabled: false }, + * }, + * ], + * wallet, + * }) + * ``` + */ + applyChainUpdates(opts: EVMExecuteParams): Promise { + return this.#applyChainUpdates.execute(this.chain, opts) + } + + /** + * Builds an unsigned pool `applyChainUpdates` tx (for multisig / offline signing), configuring, + * enabling and disabling the pool's remote lanes: remote token, remote pool(s), and both + * directional rate limits. + * + * @remarks **The parameter shape is version-discriminated**, because the contract's own + * signature changed at v1.5.1 — this is the one CCT pool write where the caller must say which + * generation it is writing for, via `opts.version`: + * + * - `version: '1.5.0'` — a single `chains` array. Each entry carries the enable/disable bit + * inline (`allowed: false` removes the lane) and a **singular** `remotePoolAddress`. + * - `version: '1.5.1'` — removals in `remoteChainSelectorsToRemove`, additions in `chainsToAdd`, + * and each addition carries **plural** `remotePoolAddresses`. This is also the shape for + * v1.6.1 and v2.0.0 pools, whose calldata is byte-identical to v1.5.1's. + * + * The declaration is checked against the pool's on-chain `typeAndVersion`, so writing the wrong + * shape is a parameter error here rather than a tx that reverts on an unknown selector (the two + * signatures have different selectors: `0xdb6327dc` vs `0xe8a1da17`). + * + * Rate limits use the SDK's `enabled` spelling, not the ABI's `isEnabled`, matching the Solana + * counterpart; amounts are in the token's smallest unit. Pass `opts.sender` to pre-flight it + * against the pool's `owner()` — `applyChainUpdates` is `onlyOwner`. + * @throws {@link CCTParamsInvalidError} if any param is invalid, `version` does not match the + * pool's own generation, or `sender` is not the pool owner. An enabled rate limiter must have + * `rate <= capacity` on every version; on a **v1.5.0 or v1.5.1** pool the bound is stricter + * (`0 < rate < capacity`), so a `rate` of `0n` or a `rate` equal to `capacity` is also rejected + * there — v1.6.1 and v2.0.0 allow both. + * + * Each lane array must also be dense (no holes) and free of repeated selectors, and a lane + * being *added* may not use the `0n` selector — the contract would accept it as a permanently + * unroutable lane rather than reverting. `remoteChainSelectorsToRemove` still accepts `0n`, so + * a pool already holding such a lane can be repaired; listing one selector in both + * `chainsToAdd` and `remoteChainSelectorsToRemove` remains the wholesale-replace idiom. + * @throws {@link CCTContractTypeInvalidError} if `poolAddress` is not a supported pool type + * @throws {@link CCTContractVersionUnsupportedError} if the pool reports an unknown version + * @example Enabling a lane on a v1.6.1 pool (the `1.5.1` shape) while retiring an old one: + * ```typescript + * const unsigned = await cct.generateUnsignedApplyChainUpdates({ + * version: '1.5.1', + * poolAddress: '0xPool...', + * sender: '0xPoolOwner...', + * remoteChainSelectorsToRemove: [3478487238524512106n], // arbitrum-sepolia + * chainsToAdd: [ + * { + * remoteChainSelector: 16015286601757825753n, // ethereum-sepolia + * remoteTokenAddress: '0xRemoteToken...', + * remotePoolAddresses: ['0xRemotePool...'], + * inboundRateLimiterConfig: { enabled: true, capacity: 100_000_000n, rate: 167_000n }, + * outboundRateLimiterConfig: { enabled: false }, + * }, + * ], + * }) + * ``` + * @example Disabling a lane on a v1.5.0 pool, where removal is `allowed: false`: + * ```typescript + * const unsigned = await cct.generateUnsignedApplyChainUpdates({ + * version: '1.5.0', + * poolAddress: '0xLegacyPool...', + * chains: [ + * { + * remoteChainSelector: 16015286601757825753n, + * allowed: false, + * remoteTokenAddress: '0xRemoteToken...', + * remotePoolAddress: '0xRemotePool...', // still required, ignored by the contract + * inboundRateLimiterConfig: { enabled: false }, + * outboundRateLimiterConfig: { enabled: false }, + * }, + * ], + * }) + * ``` + */ + generateUnsignedApplyChainUpdates(opts: ApplyChainUpdatesParams): Promise { + return this.#applyChainUpdates.generate(this.chain, opts) + } + + /** + * Builds an unsigned pool `applyAllowlistUpdates` tx (for multisig / offline signing): removes + * and adds entries in the pool's sender allowlist in one call. Probes the pool's on-chain + * `typeAndVersion` to resolve its interface + encoder. + * @remarks **v1.5.0–v1.6.1 only.** The allowlist feature does not exist on a v2.0.0 pool, which + * declares neither `applyAllowListUpdates` nor `getAllowList`/`getAllowListEnabled`, so a 2.0.0 + * pool is reported unsupported rather than emitting calldata for a removed selector. + * + * `removes` are applied *before* `adds` on-chain. Both arrays must be non-empty in total, hold + * no duplicates and no zero address, and share no address — an address in both would end up + * allowlisted (removes run first), which no caller can reasonably have meant. + * + * The pool must have been deployed **with** an allowlist (`allowlistEnabled` is immutable, and + * the call reverts `AllowListNotEnabled` when false), and the update must actually change + * state: the current allowlist is read first, and an entry the pool would silently ignore — a + * `removes` that is not allowlisted, an `adds` that already is — is rejected here. + * + * Owner-only (`applyAllowListUpdates` is `onlyOwner`). When `sender` is supplied it is checked + * against the pool's `owner()` before any calldata is built; omit it and no owner read is made + * (nothing to compare against). + * @throws {@link CCTOperationUnsupportedError} on a **v2.0.0** pool, which has no allowlist + * @throws {@link CCTParamsInvalidError} if any param is invalid, `poolAddress` is the zero + * address, both arrays are empty, an array holds duplicates or the zero address, an address + * appears in both arrays, the pool has no allowlist enabled, a `removes` entry is not currently + * allowlisted, an `adds` entry already is, or `sender` is given and is not the pool owner + * @throws {@link CCTContractVersionUnsupportedError} if the pool reports an unknown version + * @example + * ```typescript + * // build only — sign later (multisig / offline). `sender` must be the pool owner. + * const unsigned = await cct.generateUnsignedApplyAllowlistUpdates({ + * poolAddress: '0xPool...', + * removes: ['0xRevoked...'], + * adds: ['0xNewSender...'], + * sender: '0xOwner...', + * }) + * ``` + */ + generateUnsignedApplyAllowlistUpdates(opts: ApplyAllowlistUpdatesParams): Promise { + return this.#applyAllowlistUpdates.generate(this.chain, opts) + } + + /** + * Removes and adds entries in the pool's sender allowlist, signing + submitting with + * `opts.wallet`. `sender` defaults to the wallet's address and must equal it — the wallet must + * be the pool owner. + * + * `removes` are applied *before* `adds` on-chain, so an address listed in both would end up + * allowlisted; that is rejected, as are duplicates and the zero address. The pool must have an + * allowlist enabled (`allowlistEnabled` is immutable — a pool deployed without one can never + * gain it), and every entry must change state: the current allowlist is read first, and a + * `removes` that is not allowlisted or an `adds` that already is fails here rather than mining + * as a no-op. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTOperationUnsupportedError} on a v2.0.0 pool, which has no allowlist + * @throws {@link CCTParamsInvalidError} if any param is invalid, `sender` is given and is not + * the wallet's address, the wallet is not the pool owner, the pool has no allowlist enabled, or + * an entry would be a no-op (see {@link EVMTokenManager.generateUnsignedApplyAllowlistUpdates}) + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCTTxNotConfirmedError} if it is not confirmed in time + * @example + * ```typescript + * const { hash } = await cct.applyAllowlistUpdates({ + * poolAddress: '0xPool...', + * removes: ['0xRevoked...'], + * adds: ['0xNewSender...'], + * wallet, + * }) + * ``` + */ + applyAllowlistUpdates( + opts: EVMExecuteParams, + ): Promise { + return this.#applyAllowlistUpdates.execute(this.chain, opts) + } +} + +export * from '../errors.ts' +export type { AcceptAdminParams } from './token-admin-registry/operations/accept-admin.ts' +export type { + RegisterAdminMethod, + RegisterAdminParams, +} from './token-admin-registry/operations/register-admin.ts' +export type { + GetTokenAdminRegistryParams, + GetTokenAdminRegistryResult, +} from './token-admin-registry/operations/get-token-admin-registry.ts' +export type { SetPoolParams } from './token-admin-registry/operations/set-pool.ts' +export type { TransferAdminParams } from './token-admin-registry/operations/transfer-admin.ts' +export type { + GetSupportedTokensParams, + GetSupportedTokensResult, +} from './token-admin-registry/operations/get-supported-tokens.ts' +export * from './token-admin-registry/contracts.ts' +export type { DeployTokenParams } from './token/operations/deploy-token.ts' +export type { GrantMintAndBurnRolesParams } from './token/operations/grant-mint-and-burn-roles.ts' +export type { GrantMintRoleParams } from './token/operations/grant-mint-role.ts' +export type { GrantBurnRoleParams } from './token/operations/grant-burn-role.ts' +export type { RevokeMintRoleParams } from './token/operations/revoke-mint-role.ts' +export type { RevokeBurnRoleParams } from './token/operations/revoke-burn-role.ts' +export type { MintParams } from './token/operations/mint.ts' +export type { GetMintersParams, GetMintersResult } from './token/operations/get-minters.ts' +export type { GetBurnersParams, GetBurnersResult } from './token/operations/get-burners.ts' +export type { IsMinterParams, IsMinterResult } from './token/operations/is-minter.ts' +export type { IsBurnerParams, IsBurnerResult } from './token/operations/is-burner.ts' +export * from './token/contracts.ts' +export type { + DeployTokenPoolParams, + DeployableTokenPoolType, +} from './token-pool/operations/deploy-token-pool.ts' +export type { + BurnMintTokenPoolStateV2_0_0, + GetTokenPoolStateParams, + GetTokenPoolStateResult, + LegacyTokenPoolState, + LockReleaseTokenPoolStateV2_0_0, + TokenPoolStateV2_0_0, +} from './token-pool/operations/get-token-pool-state.ts' +export type { + GetTokenPoolRemotesParams, + GetTokenPoolRemotesResult, +} from './token-pool/operations/get-token-pool-remotes.ts' +export type { SetRemotePoolParams } from './token-pool/operations/set-remote-pool.ts' +export type { AddRemotePoolParams } from './token-pool/operations/add-remote-pool.ts' +export type { RemoveRemotePoolParams } from './token-pool/operations/remove-remote-pool.ts' +export type { + ApplyChainUpdatesParamVersion, + ApplyChainUpdatesParams, + ApplyChainUpdatesParamsV1_5_0, + ApplyChainUpdatesParamsV1_5_1, + ChainUpdateV1_5_0, + ChainUpdateV1_5_1, +} from './token-pool/operations/apply-chain-updates.ts' +export type { ApplyAllowlistUpdatesParams } from './token-pool/operations/apply-allowlist-updates.ts' +/** + * `GetTokenPoolRemotesResult` is a `Record`, so a caller cannot name a + * single lane's type without these. Declared in `../../chain.ts` (shared with the core + * `Chain.getTokenPoolRemotes`), re-exported here so this entry point is self-sufficient. + */ +export type { RateLimiterState, TokenPoolRemote } from '../../chain.ts' +export type { + ChainRateLimitUpdate, + SetChainRateLimiterConfigsParams, +} from './token-pool/operations/set-chain-rate-limiter-configs.ts' +export type { RateLimitConfig } from './token-pool/rate-limit.ts' +export * from './token-pool/contracts.ts' +export type { DeployLockboxParams } from './lockbox/operations/deploy-lockbox.ts' +export type { AuthorizeLockboxCallersParams } from './lockbox/operations/authorize-callers.ts' +export * from './lockbox/contracts.ts' +export type { + DeployArtifact, + DeployResult, + EVMExecuteParams, + ExplorerVerificationInput, +} from './operation.ts' +export type { TransactionResult } from '../operation.ts' diff --git a/ccip-sdk/src/cct/evm/lockbox/contracts.ts b/ccip-sdk/src/cct/evm/lockbox/contracts.ts new file mode 100644 index 000000000..afbe70862 --- /dev/null +++ b/ccip-sdk/src/cct/evm/lockbox/contracts.ts @@ -0,0 +1,29 @@ +/** + * EVM lockbox contract layer for CCT: the cached `ERC20LockBox` {@link Interface} + * ({@link LOCKBOX_INTERFACE}) for calldata encoding, and its deploy artifact + * ({@link getLockboxArtifact}). Only one lockbox version is deployable, so there is no version + * framework here. Mirrors `token/contracts.ts`. + * + * @packageDocumentation + */ + +import { Interface } from 'ethers' + +import ERC20_LOCKBOX_V2_0_0_ABI from '../artifacts/abi/V2_0_0/erc20-lockbox.ts' +import ERC20_LOCKBOX_V2_0_0_BYTECODE from '../artifacts/bytecode/V2_0_0/erc20-lockbox.ts' +import type { DeployArtifact } from '../operation.ts' + +/** Shared, cached `ERC20LockBox` interface for constructor and calldata encoding. */ +export const LOCKBOX_INTERFACE = new Interface(ERC20_LOCKBOX_V2_0_0_ABI) + +/** `ERC20LockBox` creation bytecode for `deployLockbox`. */ +export const LOCKBOX_BYTECODE = ERC20_LOCKBOX_V2_0_0_BYTECODE + +/** `ERC20LockBox` deploy artifact: contract name + ctor {@link Interface} + creation bytecode. */ +export function getLockboxArtifact(): DeployArtifact { + return { + contract: 'ERC20LockBox', + iface: LOCKBOX_INTERFACE, + bytecode: LOCKBOX_BYTECODE, + } +} diff --git a/ccip-sdk/src/cct/evm/lockbox/operations/authorize-callers.test.ts b/ccip-sdk/src/cct/evm/lockbox/operations/authorize-callers.test.ts new file mode 100644 index 000000000..02717a873 --- /dev/null +++ b/ccip-sdk/src/cct/evm/lockbox/operations/authorize-callers.test.ts @@ -0,0 +1,265 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { ZeroAddress, getIcapAddress, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { AuthorizeLockboxCallers } from './authorize-callers.ts' + +const SENDER = '0x' + '11'.repeat(20) +const LOCKBOX = '0x' + '66'.repeat(20) +const POOL = '0x' + '77'.repeat(20) +const OTHER = '0x' + '88'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +// applyAuthorizedCallerUpdates selector, per the vendored ABI (spec-pinned). +const SELECTOR = '0x91a2749a' +// Golden vectors: full literal calldata, hand-encoded from the ABI layout of +// applyAuthorizedCallerUpdates((address[] addedCallers, address[] removedCallers)) — a dynamic +// tuple of two dynamic address[] arrays. Pinning the whole byte string (rather than re-encoding +// through the SDK's own ABI) anchors every caller's position, so an added/removed swap or an +// ABI-ordering regression is caught instead of being mirrored into the expectation. +const W_TUPLE = '0000000000000000000000000000000000000000000000000000000000000020' // -> tuple +const OFF_40 = '0000000000000000000000000000000000000000000000000000000000000040' +const OFF_60 = '0000000000000000000000000000000000000000000000000000000000000060' +const OFF_80 = '0000000000000000000000000000000000000000000000000000000000000080' +const LEN_0 = '0000000000000000000000000000000000000000000000000000000000000000' +const LEN_1 = '0000000000000000000000000000000000000000000000000000000000000001' +// 20-byte address left-padded to a 32-byte word. +const word = (addr: string) => '000000000000000000000000' + addr.slice(2) + +/** Minimal EVMChain stub — the build path ignores it; execute uses only these. */ +function stubChain(): EVMChain { + return { + provider: {} as never, + logger: { debug() {}, info() {}, warn() {}, error() {} }, + nextNonce: async () => 0, + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +/** Fake ethers Signer for a plain (non-deployment) tx. */ +function fakeSigner(opts: { waitError?: Error } = {}) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(SENDER), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => + opts.waitError + ? Promise.reject(opts.waitError) + : Promise.resolve({ status: 1, contractAddress: null }), + }), + } +} + +describe('AuthorizeLockboxCallers (cct/evm lockbox operation)', () => { + describe('generate (golden vectors)', () => { + it('encodes an added caller as a call to the lockbox', async () => { + const unsigned = await new AuthorizeLockboxCallers().generate(stubChain(), { + lockbox: LOCKBOX, + addedCallers: [POOL], + sender: SENDER, + }) + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + const tx = unsigned.transactions[0]! + assert.equal(tx.to, LOCKBOX) + assert.equal(tx.from, SENDER) + assert.ok( + tx.data!.startsWith(SELECTOR), + 'data carries the applyAuthorizedCallerUpdates selector', + ) + // addedCallers:[POOL], removedCallers:[] — added array holds POOL, removed is empty. + assert.equal(tx.data, SELECTOR + W_TUPLE + OFF_40 + OFF_80 + LEN_1 + word(POOL) + LEN_0) + }) + + it('encodes both added and removed callers', async () => { + const unsigned = await new AuthorizeLockboxCallers().generate(stubChain(), { + lockbox: LOCKBOX, + addedCallers: [POOL], + removedCallers: [OTHER], + }) + // POOL sits in the added array, OTHER in the removed array — swapping them changes these bytes. + assert.equal( + unsigned.transactions[0]!.data, + SELECTOR + W_TUPLE + OFF_40 + OFF_80 + LEN_1 + word(POOL) + LEN_1 + word(OTHER), + ) + }) + + it('defaults omitted caller arrays to empty', async () => { + const unsigned = await new AuthorizeLockboxCallers().generate(stubChain(), { + lockbox: LOCKBOX, + removedCallers: [OTHER], + }) + // addedCallers omitted -> empty; OTHER lands in the removed array (removed offset is 0x60). + assert.equal( + unsigned.transactions[0]!.data, + SELECTOR + W_TUPLE + OFF_40 + OFF_60 + LEN_0 + LEN_1 + word(OTHER), + ) + }) + + it('omits `from` when no sender is given', async () => { + const unsigned = await new AuthorizeLockboxCallers().generate(stubChain(), { + lockbox: LOCKBOX, + addedCallers: [POOL], + }) + assert.equal(unsigned.transactions[0]!.from, undefined) + }) + }) + + describe('validation', () => { + it('rejects an invalid lockbox address', async () => { + await assert.rejects( + () => + new AuthorizeLockboxCallers().generate(stubChain(), { + lockbox: 'nope', + addedCallers: [POOL], + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'authorizeLockboxCallers' && + err.context.param === 'lockbox', + ) + }) + + it('rejects a zero-address lockbox', async () => { + // a call to 0x0 hits no code, so it would mine as a successful no-op + await assert.rejects( + () => + new AuthorizeLockboxCallers().generate(stubChain(), { + lockbox: ZeroAddress, + addedCallers: [POOL], + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'authorizeLockboxCallers' && + err.context.param === 'lockbox', + ) + }) + + it('rejects the zero address written in ICAP form', async () => { + // isAddress() accepts ICAP, and this never equals ZeroAddress literally + await assert.rejects( + () => + new AuthorizeLockboxCallers().generate(stubChain(), { + lockbox: getIcapAddress(ZeroAddress), + addedCallers: [POOL], + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'lockbox', + ) + }) + + it('rejects when no callers are supplied', async () => { + await assert.rejects( + () => new AuthorizeLockboxCallers().generate(stubChain(), { lockbox: LOCKBOX }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'addedCallers', + ) + }) + + it('rejects when both caller arrays are empty', async () => { + await assert.rejects( + () => + new AuthorizeLockboxCallers().generate(stubChain(), { + lockbox: LOCKBOX, + addedCallers: [], + removedCallers: [], + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'addedCallers', + ) + }) + + it('rejects an invalid added caller address', async () => { + await assert.rejects( + () => + new AuthorizeLockboxCallers().generate(stubChain(), { + lockbox: LOCKBOX, + addedCallers: [POOL, 'nope'], + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'addedCallers[1]', + ) + }) + + it('rejects an invalid removed caller address', async () => { + await assert.rejects( + () => + new AuthorizeLockboxCallers().generate(stubChain(), { + lockbox: LOCKBOX, + removedCallers: ['nope'], + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'removedCallers[0]', + ) + }) + + it('rejects the zero address as a caller', async () => { + await assert.rejects( + () => + new AuthorizeLockboxCallers().generate(stubChain(), { + lockbox: LOCKBOX, + addedCallers: [ZeroAddress], + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'addedCallers[0]', + ) + }) + + it('rejects an invalid sender', async () => { + await assert.rejects( + () => + new AuthorizeLockboxCallers().generate(stubChain(), { + lockbox: LOCKBOX, + addedCallers: [POOL], + sender: 'nope', + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new AuthorizeLockboxCallers().execute(stubChain(), { + lockbox: LOCKBOX, + addedCallers: [POOL], + wallet: fakeSigner(), + }) + assert.deepEqual(result, { hash: HASH }) + }) + + it('throws CCIPExecTxRevertedError when the tx reverts on-chain', async () => { + await assert.rejects( + () => + new AuthorizeLockboxCallers().execute(stubChain(), { + lockbox: LOCKBOX, + addedCallers: [POOL], + wallet: fakeSigner({ waitError: makeError('execution reverted', 'CALL_EXCEPTION') }), + }), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && + err.context.operation === 'authorizeLockboxCallers', + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => + new AuthorizeLockboxCallers().execute(stubChain(), { + lockbox: LOCKBOX, + addedCallers: [POOL], + wallet: {}, + }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/lockbox/operations/authorize-callers.ts b/ccip-sdk/src/cct/evm/lockbox/operations/authorize-callers.ts new file mode 100644 index 000000000..003ddb5f5 --- /dev/null +++ b/ccip-sdk/src/cct/evm/lockbox/operations/authorize-callers.ts @@ -0,0 +1,67 @@ +/** + * authorizeLockboxCallers — adds/removes authorized callers on an `ERC20LockBox` (v2.0.0) via + * `applyAuthorizedCallerUpdates`. A `LockReleaseTokenPool` must be an authorized caller of its + * lockbox before it can lock/release. Mirrors `token-pool/operations/transfer-ownership.ts`. + * + * @packageDocumentation + */ + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { EVMOperation, callTx } from '../../operation.ts' +import { validateNonZeroAddress } from '../../validate.ts' +import { LOCKBOX_INTERFACE } from '../contracts.ts' + +/** + * Parameters for {@link AuthorizeLockboxCallers}. At least one caller across both arrays is required. + * @remarks `AuthorizedCallers._applyAuthorizedCallerUpdates` applies `removedCallers` first, so an + * address in both arrays ends up authorized. The list is a set: re-adding an existing caller is a + * no-op (though `AuthorizedCallerAdded` still fires), and removing an absent one emits nothing. + */ +export interface AuthorizeLockboxCallersParams { + /** Address of the `ERC20LockBox` to update. */ + lockbox: string + /** Callers to authorize (e.g. the `LockReleaseTokenPool`); defaults to `[]`. */ + addedCallers?: string[] + /** Callers to deauthorize; defaults to `[]`. */ + removedCallers?: string[] + /** Lockbox owner; sets `tx.from` for offline / multisig signing. */ + sender?: string +} + +/** Applies authorized-caller updates on an `ERC20LockBox` via `applyAuthorizedCallerUpdates`. */ +export class AuthorizeLockboxCallers extends EVMOperation { + readonly name = 'authorizeLockboxCallers' + + /** Validates the lockbox and every caller address; requires at least one caller. */ + protected override validate({ + lockbox, + addedCallers = [], + removedCallers = [], + }: AuthorizeLockboxCallersParams): void { + validateNonZeroAddress(this.name, 'lockbox', lockbox) + if (addedCallers.length + removedCallers.length === 0) { + throw new CCTParamsInvalidError( + this.name, + 'addedCallers', + 'at least one caller must be added or removed', + ) + } + const validateCaller = (field: string, c: string, i: number): void => + validateNonZeroAddress(this.name, `${field}[${i}]`, c) + addedCallers.forEach((c, i) => validateCaller('addedCallers', c, i)) + removedCallers.forEach((c, i) => validateCaller('removedCallers', c, i)) + } + + /** Builds `applyAuthorizedCallerUpdates` calldata targeting the lockbox. */ + protected buildUnsigned( + _chain: EVMChain, + { lockbox, addedCallers = [], removedCallers = [] }: AuthorizeLockboxCallersParams, + ): UnsignedEVMTx { + const data = LOCKBOX_INTERFACE.encodeFunctionData('applyAuthorizedCallerUpdates', [ + { addedCallers, removedCallers }, + ]) + return callTx(lockbox, data) + } +} diff --git a/ccip-sdk/src/cct/evm/lockbox/operations/deploy-lockbox.test.ts b/ccip-sdk/src/cct/evm/lockbox/operations/deploy-lockbox.test.ts new file mode 100644 index 000000000..3dbd48b9f --- /dev/null +++ b/ccip-sdk/src/cct/evm/lockbox/operations/deploy-lockbox.test.ts @@ -0,0 +1,152 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { CCTParamsInvalidError, CCTTxFailedError } from '../../../errors.ts' +import ERC20_LOCKBOX_V2_0_0_ABI from '../../artifacts/abi/V2_0_0/erc20-lockbox.ts' +import LOCKBOX_V2_0_0 from '../../artifacts/bytecode/V2_0_0/erc20-lockbox.ts' +import { DeployLockbox } from './deploy-lockbox.ts' + +const SENDER = '0x' + '11'.repeat(20) +const TOKEN = '0x' + '22'.repeat(20) +const DEPLOYED = '0x' + '77'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +// Golden vector: the ctor arg is a single 32-byte word holding the token address. Computed +// independently with a fresh ethers Interface so it guards the SDK's init-code against drift. +const W_TOKEN = '0000000000000000000000002222222222222222222222222222222222222222' +const CTOR_ARGS = new Interface(ERC20_LOCKBOX_V2_0_0_ABI).encodeDeploy([TOKEN]) + +/** Minimal EVMChain stub — deployLockbox's build path ignores it; execute uses only these. */ +function stubChain(): EVMChain { + return { + provider: {} as never, + logger: { debug() {}, info() {}, warn() {}, error() {} }, + nextNonce: async () => 0, + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +/** Fake ethers Signer whose deployment receipt carries `contractAddress`. */ +function fakeSigner(opts: { contractAddress?: string | null; waitError?: Error }) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(SENDER), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => + opts.waitError + ? Promise.reject(opts.waitError) + : Promise.resolve({ + status: 1, + contractAddress: + opts.contractAddress === undefined ? DEPLOYED : opts.contractAddress, + }), + }), + } +} + +describe('DeployLockbox (cct/evm lockbox operation)', () => { + describe('generate (golden vector)', () => { + it('builds the lockbox as init-code with no `to`', async () => { + const unsigned = await new DeployLockbox().generate(stubChain(), { + token: TOKEN, + sender: SENDER, + }) + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + const tx = unsigned.transactions[0]! + assert.equal(tx.to, undefined, 'deployment tx has no `to`') + assert.equal(tx.from, SENDER) + assert.ok(tx.data!.startsWith(LOCKBOX_V2_0_0), 'data starts with creation bytecode') + // Pinned bytes: the constructor arg is exactly the token address, left-padded to 32 bytes. + assert.equal(CTOR_ARGS, '0x' + W_TOKEN) + assert.equal(tx.data, LOCKBOX_V2_0_0 + W_TOKEN) + }) + + it('omits `from` when no sender is given', async () => { + const unsigned = await new DeployLockbox().generate(stubChain(), { token: TOKEN }) + assert.equal(unsigned.transactions[0]!.from, undefined) + }) + }) + + describe('validation', () => { + it('rejects an invalid token address', async () => { + await assert.rejects( + () => new DeployLockbox().generate(stubChain(), { token: 'nope' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'deployLockbox' && + err.context.param === 'token', + ) + }) + + it('rejects the zero address for token', async () => { + await assert.rejects( + () => new DeployLockbox().generate(stubChain(), { token: ZeroAddress }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'token', + ) + }) + + it('rejects an invalid sender', async () => { + await assert.rejects( + () => new DeployLockbox().generate(stubChain(), { token: TOKEN, sender: 'nope' }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + }) + + describe('execute', () => { + it('deploys and returns the tx hash and deployed address', async () => { + const result = await new DeployLockbox().execute(stubChain(), { + token: TOKEN, + wallet: fakeSigner({ contractAddress: DEPLOYED }), + }) + assert.deepEqual(result, { + hash: HASH, + contractAddress: DEPLOYED, + verification: { contract: 'ERC20LockBox', encodedConstructorArgs: '0x' + W_TOKEN }, + }) + }) + + it('throws CCTTxFailedError when the receipt carries no contract address', async () => { + await assert.rejects( + () => + new DeployLockbox().execute(stubChain(), { + token: TOKEN, + wallet: fakeSigner({ contractAddress: null }), + }), + (err: unknown) => + err instanceof CCTTxFailedError && + err.context.operation === 'deployLockbox' && + !err.isTransient, + ) + }) + + it('throws CCIPExecTxRevertedError when the deployment reverts on-chain', async () => { + await assert.rejects( + () => + new DeployLockbox().execute(stubChain(), { + token: TOKEN, + wallet: fakeSigner({ waitError: makeError('execution reverted', 'CALL_EXCEPTION') }), + }), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && err.context.operation === 'deployLockbox', + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => new DeployLockbox().execute(stubChain(), { token: TOKEN, wallet: {} }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/lockbox/operations/deploy-lockbox.ts b/ccip-sdk/src/cct/evm/lockbox/operations/deploy-lockbox.ts new file mode 100644 index 000000000..fbbe6d476 --- /dev/null +++ b/ccip-sdk/src/cct/evm/lockbox/operations/deploy-lockbox.ts @@ -0,0 +1,42 @@ +/** + * deployLockbox — deploys an `ERC20LockBox` (v2.0.0) via raw init-code. The tx has no + * `to`; `execute` returns the deployed contract address. A lockbox escrows a single token + * for `LockReleaseTokenPool`s; deploy it before the pool, then authorize the pool on it via + * {@link AuthorizeLockboxCallers}. Mirrors `token-pool/operations/deploy-token-pool.ts`. + * + * @packageDocumentation + */ + +import type { Interface } from 'ethers' + +import { type DeployArtifact, EVMDeployOperation } from '../../operation.ts' +import { validateNonZeroAddress } from '../../validate.ts' +import { getLockboxArtifact } from '../contracts.ts' + +/** Parameters for {@link DeployLockbox} — deploys `ERC20LockBox` (v2.0.0). */ +export interface DeployLockboxParams { + /** Address of the token the lockbox escrows; the v2.0.0 constructor reverts on the zero address. */ + token: string + /** Deployer address; sets `tx.from` for offline / multisig signing. */ + sender?: string +} + +/** Deploys an `ERC20LockBox`; `execute` resolves to `{ hash, contractAddress, verification }`. */ +export class DeployLockbox extends EVMDeployOperation { + readonly name = 'deployLockbox' + + /** Validates the constructor params before building init-code. */ + protected override validate(params: DeployLockboxParams): void { + validateNonZeroAddress(this.name, 'token', params.token) + } + + /** Deploy artifact for `ERC20LockBox` (v2.0.0). */ + protected artifact(): DeployArtifact { + return getLockboxArtifact() + } + + /** ABI-encodes the `ERC20LockBox` (v2.0.0) constructor args. */ + protected encode(iface: Interface, p: DeployLockboxParams): string { + return iface.encodeDeploy([p.token]) + } +} diff --git a/ccip-sdk/src/cct/evm/operation.ts b/ccip-sdk/src/cct/evm/operation.ts new file mode 100644 index 000000000..eb4b4eb73 --- /dev/null +++ b/ccip-sdk/src/cct/evm/operation.ts @@ -0,0 +1,191 @@ +/** + * EVM {@link Operation} lifecycle: prepare (validate → parse) → encode → submit, plus the shared + * wallet-sender pre-flight ({@link EVMOperation.resolveWalletSender}). Deployment ops extend + * {@link EVMDeployOperation}, which also resolves the deployed address. + * + * @remarks The pool-owner pre-flight lives in the token-pool layer as a free helper + * (`assertPoolOwner` in `token-pool/contracts.ts`), so this generic base + * carries no dependency on a specific operation. + * + * @packageDocumentation + */ + +import { type Interface, getAddress } from 'ethers' + +import { CCIPWalletInvalidError } from '../../errors/index.ts' +import { type EVMChain, isSigner } from '../../evm/index.ts' +import type { UnsignedEVMTx } from '../../evm/types.ts' +import { ChainFamily } from '../../networks.ts' +import { CCTParamsInvalidError, CCTTxFailedError } from '../errors.ts' +import { type ExecuteParams, type TransactionResult, Operation } from '../operation.ts' +import { submit } from './submit.ts' +import { validateAddress } from './validate.ts' + +/** Assembles a contract-deployment tx (no `to`): creation bytecode + ABI-encoded ctor args. */ +export function deployTx(bytecode: `0x${string}`, ctorArgs: string): UnsignedEVMTx { + return { family: ChainFamily.EVM, transactions: [{ data: bytecode + ctorArgs.slice(2) }] } +} + +/** Assembles an unsigned call to an existing contract: `to` + ABI-encoded calldata. */ +export function callTx(to: string, data: string): UnsignedEVMTx { + return { family: ChainFamily.EVM, transactions: [{ to, data }] } +} + +/** + * The deploy-side inputs a block explorer needs to verify a contract's source: its name and + * ABI-encoded constructor args, captured while deploying with no extra RPC. + * @remarks A constructor-args companion, *not* proof of verification — nothing here is read back + * from the chain or submitted anywhere. A full submission also needs the source/compiler side + * (standard-json input plus the matching solc version and settings), which this SDK does not + * vendor; those ship in the `@chainlink/contracts-ccip` package. + * + * Only available from `execute`, which deploys and so learns the address. The + * `generateUnsigned*` builders return the unsigned tx alone. + * @example Verifying on Etherscan, whose "Constructor Arguments" field wants the args bare: + * ```typescript + * const { contractAddress, verification } = await cct.deployTokenPool({ ...params, wallet }) + * console.log(verification.contract) // 'LockReleaseTokenPool' + * console.log(verification.encodedConstructorArgs.slice(2)) // drop the `0x` + * ``` + */ +export interface ExplorerVerificationInput { + /** Contract name as compiled, e.g. `BurnMintTokenPool`; unqualified, matching the artifact. */ + contract: string + /** 0x-prefixed ABI-encoded constructor args, or just `0x` when the constructor takes none. */ + encodedConstructorArgs: string +} + +/** + * A contract deploy artifact: the contract name (for verification), the cached constructor + * {@link Interface}, and the creation bytecode. Field is `iface` (not `interface`, a reserved word). + */ +export interface DeployArtifact { + contract: string + iface: Interface + bytecode: `0x${string}` +} + +/** EVM {@link ExecuteParams} — EVM ops need nothing beyond the signing `wallet`. */ +export type EVMExecuteParams

= ExecuteParams

+ +/** + * Result of a successful EVM deployment write: the tx hash plus the deployed + * contract address (token, pool, etc.). Also carries the + * {@link ExplorerVerificationInput} needed to verify the contract's source on a + * block explorer — additive, so readers of `{ hash, contractAddress }` are unaffected. + */ +export type DeployResult = TransactionResult & { + contractAddress: string + verification: ExplorerVerificationInput +} + +/** + * EVM CCT write base. Subclasses supply {@link parse} (or {@link validate}) and + * {@link buildUnsigned}; {@link execute} signs and submits, returning the confirmed tx hash. Ops + * that resolve to more (e.g. a deployed address) extend {@link EVMDeployOperation}. + */ +export abstract class EVMOperation

extends Operation< + EVMChain, + P, + UnsignedEVMTx, + TransactionResult, + Parsed +> { + /** Build calldata into an unsigned tx; versioned ops resolve their encoder here. */ + protected abstract buildUnsigned( + chain: EVMChain, + params: Parsed, + ): Promise | UnsignedEVMTx + + /** Run {@link prepare} and {@link buildUnsigned}, applying optional `sender`; no signing. */ + async generate(chain: EVMChain, params: P): Promise { + const parsed = this.prepare(params) + if (params.sender !== undefined) validateAddress(this.name, 'sender', params.sender) + const unsigned = await this.buildUnsigned(chain, parsed) + if (params.sender && unsigned.transactions[0]) unsigned.transactions[0].from = params.sender + return unsigned + } + + /** + * Resolves the address a signed submission is authorized against: the signing wallet's own. + * The chain gates on `msg.sender`, and {@link submit} clears any builder-set `tx.from` before + * populating the tx (so ethers' own from/signer guard never fires) — an explicit `sender` that + * differs from the wallet would therefore let an op's pre-tx checks authorize one address while + * a different one actually signs, passing every local guard and reverting on-chain. Ops that + * gate on an on-chain role call this from `execute`; build with `generateUnsigned*` instead + * when the eventual signer isn't known yet, where `sender` is trusted as given. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address + */ + protected async resolveWalletSender(wallet: unknown, sender?: string): Promise { + if (!isSigner(wallet)) throw new CCIPWalletInvalidError(wallet) + const walletAddress = await wallet.getAddress() + if (sender === undefined) return walletAddress + // Validated before `getAddress`, which throws a raw ethers TypeError on a malformed string. + // This runs ahead of `generate`'s own validate(), so without it the documented + // CCTParamsInvalidError contract would leak an ethers error for a bad `sender`. + validateAddress(this.name, 'sender', sender) + if (getAddress(sender) !== getAddress(walletAddress)) + throw new CCTParamsInvalidError( + this.name, + 'sender', + `must be the executing wallet address (${walletAddress}) — use generateUnsigned${this.name[0]!.toUpperCase()}${this.name.slice(1)} for externally-signed transactions`, + ) + return sender + } + + /** {@link generate}, then sign and submit; returns the confirmed tx hash. */ + async execute(chain: EVMChain, params: EVMExecuteParams

): Promise { + const { response } = await submit( + chain, + params.wallet, + await this.generate(chain, params), + this.name, + ) + return { hash: response.hash } + } +} + +/** + * EVM contract-deployment base. Subclasses supply {@link validate}, {@link artifact} (name + + * ctor {@link Interface} + creation bytecode), and {@link encode}; the base wires + * {@link buildUnsigned} (init-code = bytecode + encoded ctor args) and {@link execute} (submit, + * then read the deployed address and pair it with an {@link ExplorerVerificationInput}). + */ +export abstract class EVMDeployOperation

extends EVMOperation

{ + /** Contract name, ctor {@link Interface}, and creation bytecode for this deployment. */ + protected abstract artifact(params: P): DeployArtifact + + /** ABI-encodes the constructor args (0x-prefixed) for this deployment. */ + protected abstract encode(iface: Interface, params: P): string + + /** Builds a deployment tx (no `to`): creation bytecode + ABI-encoded constructor args. */ + protected buildUnsigned(_chain: EVMChain, params: P): UnsignedEVMTx { + const a = this.artifact(params) + return deployTx(a.bytecode, this.encode(a.iface, params)) + } + + /** + * {@link generate}, then sign and submit; resolves to the tx hash and the newly deployed + * contract address (read from the mined receipt), plus the + * {@link ExplorerVerificationInput} for verifying its source on a block explorer. + * @throws {@link CCTTxFailedError} if the tx mined without producing a contract address + */ + override async execute(chain: EVMChain, params: EVMExecuteParams

): Promise { + const unsigned = await this.generate(chain, params) + const { contract, iface } = this.artifact(params) + // Same value `buildUnsigned` appended to the bytecode. Taken from `encode` rather than + // sliced back out of the init-code, so it stays correct regardless of the tx layout. + const encodedConstructorArgs = this.encode(iface, params) + const { response, receipt } = await submit(chain, params.wallet, unsigned, this.name) + if (!receipt.contractAddress) + throw new CCTTxFailedError(this.name, 'deployment produced no contract address', { + context: { txHash: response.hash }, + }) + return { + hash: response.hash, + contractAddress: receipt.contractAddress, + verification: { contract, encodedConstructorArgs }, + } + } +} diff --git a/ccip-sdk/src/cct/evm/query.ts b/ccip-sdk/src/cct/evm/query.ts new file mode 100644 index 000000000..8406b9eb4 --- /dev/null +++ b/ccip-sdk/src/cct/evm/query.ts @@ -0,0 +1,35 @@ +/** + * EVM CCT reads: {@link Query} bound to an {@link EVMChain}, plus {@link getTypedContract}, the + * call-typed handle read ops decode through. Mirrors `cct/solana/query.ts`. + * + * @packageDocumentation + */ + +import type { Abi } from 'abitype' +import { type InterfaceAbi, Contract } from 'ethers' +import type { TypedContract } from 'ethers-abitype' + +import type { EVMChain } from '../../evm/index.ts' +import { Query } from '../query.ts' + +/** Shared base for read-only EVM CCT queries; see {@link Query}. */ +export abstract class EVMQuery

extends Query< + EVMChain, + P, + R, + Parsed +> {} + +/** + * Binds `address` to `abi` as a call-typed contract for read ops: one value both types the calls + * and builds the runtime `Interface`. + * @remarks The CCT layer's single ethers → `ethers-abitype` cast; the library's own + * `typedContract` would avoid it, but its ESM entry is unusable (`main` resolves to CJS). + */ +export function getTypedContract( + chain: EVMChain, + address: string, + abi: ABI & InterfaceAbi, +): TypedContract { + return new Contract(address, abi, chain.provider) as unknown as TypedContract +} diff --git a/ccip-sdk/src/cct/evm/submit.test.ts b/ccip-sdk/src/cct/evm/submit.test.ts new file mode 100644 index 000000000..43ab45c6c --- /dev/null +++ b/ccip-sdk/src/cct/evm/submit.test.ts @@ -0,0 +1,142 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../errors/index.ts' +import type { EVMChain } from '../../evm/index.ts' +import type { UnsignedEVMTx } from '../../evm/types.ts' +import { ChainFamily } from '../../networks.ts' +import { CCTTxFailedError, CCTTxNotConfirmedError } from '../errors.ts' +import { submit } from './submit.ts' + +const TAR = '0x' + '44'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +const UNSIGNED: UnsignedEVMTx = { + family: ChainFamily.EVM, + transactions: [{ to: TAR, data: '0x1234' }], +} + +function stubChain(): EVMChain { + return { + provider: {} as never, + logger: { debug() {}, info() {}, warn() {}, error() {} }, + nextNonce: async () => 0, + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +/** + * Fake ethers Signer. `wait` resolves to `receipt` (or rejects with `waitError`); + * `submitError` makes both send and sign paths reject (pre-broadcast failure). + */ +function fakeSigner(opts: { + receipt?: { status: number; contractAddress?: string | null } | null + waitError?: Error + submitError?: Error +}) { + const fail = opts.submitError + return { + signTransaction: () => (fail ? Promise.reject(fail) : Promise.resolve('0x')), + getAddress: () => Promise.resolve('0x' + '55'.repeat(20)), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: (_tx: unknown) => + fail + ? Promise.reject(fail) + : Promise.resolve({ + hash: HASH, + wait: (_c?: number, _t?: number) => + opts.waitError + ? Promise.reject(opts.waitError) + : Promise.resolve(opts.receipt ?? null), + }), + } +} + +describe('submit (sign-and-confirm pipeline)', () => { + it('returns the broadcast response and mined receipt', async () => { + const { response, receipt } = await submit( + stubChain(), + fakeSigner({ receipt: { status: 1, contractAddress: null } }), + UNSIGNED, + 'setPool', + ) + assert.equal(response.hash, HASH) + assert.equal(receipt.status, 1) + }) + + it('throws CCIPExecTxRevertedError (non-transient) when wait() throws CALL_EXCEPTION', async () => { + await assert.rejects( + () => + submit( + stubChain(), + fakeSigner({ waitError: makeError('execution reverted', 'CALL_EXCEPTION') }), + UNSIGNED, + 'setPool', + ), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && + err.context.operation === 'setPool' && + err.context.txHash === HASH && + !err.isTransient && + err.message.includes('reverted'), + ) + }) + + it('throws CCTTxNotConfirmedError (transient) when wait() throws TRANSACTION_REPLACED', async () => { + await assert.rejects( + () => + submit( + stubChain(), + fakeSigner({ waitError: makeError('transaction replaced', 'TRANSACTION_REPLACED') }), + UNSIGNED, + 'setPool', + ), + (err: unknown) => + err instanceof CCTTxNotConfirmedError && err.context.txHash === HASH && err.isTransient, + ) + }) + + it('throws CCTTxNotConfirmedError (transient, keeps hash) when no receipt arrives', async () => { + await assert.rejects( + () => submit(stubChain(), fakeSigner({ receipt: null }), UNSIGNED, 'setPool'), + (err: unknown) => + err instanceof CCTTxNotConfirmedError && err.context.txHash === HASH && err.isTransient, + ) + }) + + it('throws CCTTxNotConfirmedError (transient, keeps hash) on confirmation timeout', async () => { + await assert.rejects( + () => + submit( + stubChain(), + fakeSigner({ waitError: makeError('timed out', 'TIMEOUT') }), + UNSIGNED, + 'setPool', + ), + (err: unknown) => + err instanceof CCTTxNotConfirmedError && err.context.txHash === HASH && err.isTransient, + ) + }) + + it('throws a transient CCTTxFailedError when submission fails with a network error', async () => { + await assert.rejects( + () => + submit( + stubChain(), + fakeSigner({ submitError: makeError('network down', 'NETWORK_ERROR') }), + UNSIGNED, + 'setPool', + ), + (err: unknown) => err instanceof CCTTxFailedError && err.isTransient, + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => submit(stubChain(), {}, UNSIGNED, 'setPool'), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) +}) diff --git a/ccip-sdk/src/cct/evm/submit.ts b/ccip-sdk/src/cct/evm/submit.ts new file mode 100644 index 000000000..45c5dc418 --- /dev/null +++ b/ccip-sdk/src/cct/evm/submit.ts @@ -0,0 +1,95 @@ +/** + * Shared sign-and-submit pipeline for EVM CCT operations. Maps broadcast and + * confirmation failures to {@link CCTTxFailedError} / {@link CCTTxNotConfirmedError}, + * and on-chain reverts to {@link CCIPExecTxRevertedError}. Operations map the + * confirmed `{ response, receipt }` to their own result shape. + * + * @packageDocumentation + */ + +import { + type TransactionReceipt, + type TransactionRequest, + type TransactionResponse, + isError, +} from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../errors/index.ts' +import { type EVMChain, isSigner, submitTransaction } from '../../evm/index.ts' +import type { UnsignedEVMTx } from '../../evm/types.ts' +import { CCTTxFailedError, CCTTxNotConfirmedError } from '../errors.ts' + +/** Max ms to wait for one confirmation before throwing {@link CCTTxNotConfirmedError}. */ +const CONFIRM_TIMEOUT_MS = 60_000 + +/** True for ethers infra errors worth retrying (not an on-chain revert). */ +function isTransientError(error: unknown): boolean { + return ( + isError(error, 'TIMEOUT') || isError(error, 'NETWORK_ERROR') || isError(error, 'SERVER_ERROR') + ) +} + +/** + * Signs and submits the first transaction in `unsigned`, then waits for one confirmation. + * Returns the broadcast `response` and mined `receipt`; callers map these to their + * own result shape (see {@link EVMOperation.execute}). + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain + * @throws {@link CCTTxNotConfirmedError} if broadcast but not confirmed in time + */ +export async function submit( + chain: EVMChain, + wallet: unknown, + unsigned: UnsignedEVMTx, + operation: string, +): Promise<{ response: TransactionResponse; receipt: TransactionReceipt }> { + if (!isSigner(wallet)) throw new CCIPWalletInvalidError(wallet) + const sender = await wallet.getAddress() + chain.logger.debug(`${operation}: submitting...`) + + const [first] = unsigned.transactions + if (!first) throw new CCTTxFailedError(operation, 'no transaction to submit') + + let response: TransactionResponse + let nonceConsumed = false + try { + let tx: TransactionRequest = { ...first } + tx.from = undefined // drop any builder-set sender before populate, else ethers throws on a from/signer mismatch + if (tx.nonce == null) { + tx.nonce = await chain.nextNonce(sender) + nonceConsumed = true + } + tx = await wallet.populateTransaction(tx) + tx.from = undefined // some signers reject a pre-populated `from` + response = await submitTransaction(wallet, tx, chain.provider) + } catch (error) { + if (nonceConsumed) chain.rollbackNonce(sender) + throw new CCTTxFailedError(operation, error instanceof Error ? error.message : String(error), { + cause: error instanceof Error ? error : undefined, + isTransient: isTransientError(error), + }) + } + + chain.logger.debug(`${operation}: waiting for confirmation, tx =`, response.hash) + + let receipt: TransactionReceipt | null + try { + receipt = await response.wait(1, CONFIRM_TIMEOUT_MS) + } catch (error) { + if (isError(error, 'CALL_EXCEPTION')) { + // mined revert — permanent; reuse the core revert error so consumers catch + // one type across core `execute` and CCT ops. + throw new CCIPExecTxRevertedError(response.hash, { cause: error, context: { operation } }) + } + // broadcast already succeeded; any non-revert error leaves the tx in an unknown state + throw new CCTTxNotConfirmedError(operation, response.hash, { + cause: error instanceof Error ? error : undefined, + }) + } + + if (!receipt) throw new CCTTxNotConfirmedError(operation, response.hash) + + chain.logger.info(`${operation}: confirmed, tx =`, response.hash) + return { response, receipt } +} diff --git a/ccip-sdk/src/cct/evm/token-admin-registry/contracts.ts b/ccip-sdk/src/cct/evm/token-admin-registry/contracts.ts new file mode 100644 index 000000000..8893d5c89 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-admin-registry/contracts.ts @@ -0,0 +1,185 @@ +/** + * EVM token-admin-registry contract layer for CCT — the two contracts the admin ops talk to: + * + * - **`TokenAdminRegistry`** ({@link getTokenAdminRegistryInterface}) — holds each token's + * administrator/pool entry. Every admin write is gated on who currently holds those roles, so + * the ops also share one spelling of that read ({@link readTokenAdminRegistryConfig}, + * {@link isRegistryModule}) rather than each deriving a handle. + * - **`RegistryModuleOwnerCustom`** ({@link getRegistryModuleOwnerCustomInterface}) — the + * self-service module `registerAdmin` calls to propose an administrator without the registry + * owner's help. + * + * Neither is deployed by this SDK, so unlike `token/contracts.ts` and `token-pool/contracts.ts` + * there are no bytecode or {@link DeployArtifact} entries here — only interfaces and reads. + * Mirrors `lockbox/contracts.ts` in shape, `token/contracts.ts` in the version-keyed accessors. + * + * @packageDocumentation + */ + +import { Interface, getAddress } from 'ethers' +import type { TypedContract } from 'ethers-abitype' + +import type { EVMChain } from '../../../evm/index.ts' +import { resultToObject } from '../../../evm/types.ts' +import { CCTContractTypeInvalidError, CCTContractVersionUnsupportedError } from '../../errors.ts' +import REGISTRY_MODULE_OWNER_CUSTOM_V1_5_0_ABI from '../artifacts/abi/V1_5_0/registry-module-owner-custom.ts' +import TOKEN_ADMIN_REGISTRY_V1_5_0_ABI from '../artifacts/abi/V1_5_0/token-admin-registry.ts' +import REGISTRY_MODULE_OWNER_CUSTOM_V1_6_0_ABI from '../artifacts/abi/V1_6_0/registry-module-owner-custom.ts' +import { getTypedContract } from '../query.ts' + +/** + * Known `TokenAdminRegistry` versions. Only `1.5.0` is vendored: the admin surface this SDK uses + * (`getTokenConfig`, `isRegistryModule`, `proposeAdministrator`, `transferAdminRole`, + * `acceptAdminRole`, `setPool`) is byte-identical from v1.5 through v2.0, so one ABI serves them + * all and no version dispatch is needed. + */ +export const TokenAdminRegistryVersion = { + V1_5_0: '1.5.0', +} as const + +/** A known `TokenAdminRegistry` version. */ +export type TokenAdminRegistryVersion = + (typeof TokenAdminRegistryVersion)[keyof typeof TokenAdminRegistryVersion] + +/** + * Known `RegistryModuleOwnerCustom` versions, low to high. `1.6.0` added + * `registerAccessControlDefaultAdmin`; the two share `registerAdminViaOwner` and + * `registerAdminViaGetCCIPAdmin`. + */ +export const RegistryModuleOwnerCustomVersion = { + V1_5_0: '1.5.0', + V1_6_0: '1.6.0', +} as const + +/** A known `RegistryModuleOwnerCustom` version. */ +export type RegistryModuleOwnerCustomVersion = + (typeof RegistryModuleOwnerCustomVersion)[keyof typeof RegistryModuleOwnerCustomVersion] + +/** + * Cached `TokenAdminRegistry` {@link Interface}s per {@link TokenAdminRegistryVersion}, built once + * from the vendored ABI (no per-call `new Interface`). Mirrors `TOKEN_INTERFACES` in + * `token/contracts.ts`. + */ +export const TOKEN_ADMIN_REGISTRY_INTERFACES: Record = { + [TokenAdminRegistryVersion.V1_5_0]: new Interface(TOKEN_ADMIN_REGISTRY_V1_5_0_ABI), +} + +/** + * Cached `RegistryModuleOwnerCustom` {@link Interface}s per + * {@link RegistryModuleOwnerCustomVersion}, each built from its own vendored ABI. The shared + * functions encode identically at both versions, so the split is not about calldata — it is about + * *which functions exist*: only `1.6.0` knows `registerAccessControlDefaultAdmin`, so encoding it + * against the `1.5.0` interface throws instead of producing calldata a v1.5.0 module would reject. + */ +export const REGISTRY_MODULE_OWNER_CUSTOM_INTERFACES: Record< + RegistryModuleOwnerCustomVersion, + Interface +> = { + [RegistryModuleOwnerCustomVersion.V1_5_0]: new Interface(REGISTRY_MODULE_OWNER_CUSTOM_V1_5_0_ABI), + [RegistryModuleOwnerCustomVersion.V1_6_0]: new Interface(REGISTRY_MODULE_OWNER_CUSTOM_V1_6_0_ABI), +} + +/** Type guard for {@link RegistryModuleOwnerCustomVersion}. */ +export function isRegistryModuleOwnerCustomVersion( + v: string, +): v is RegistryModuleOwnerCustomVersion { + return Object.values(RegistryModuleOwnerCustomVersion).some((known) => known === v) +} + +/** `typeAndVersion` prefix every `RegistryModuleOwnerCustom` reports. */ +const REGISTRY_MODULE_OWNER_CUSTOM = 'RegistryModuleOwnerCustom' + +/** + * Resolves a deployed module's version from its `typeAndVersion`, narrowed to a known + * {@link RegistryModuleOwnerCustomVersion}. Mirrors `resolveTokenPool` in + * `token-pool/contracts.ts`. + * @throws {@link CCTContractTypeInvalidError} if `address` is not a `RegistryModuleOwnerCustom` + * @throws {@link CCTContractVersionUnsupportedError} if it reports an unknown version + */ +export async function resolveRegistryModuleOwnerCustom( + chain: EVMChain, + address: string, +): Promise { + const [contractType, version] = await chain.typeAndVersion(address) + if (contractType !== REGISTRY_MODULE_OWNER_CUSTOM) + throw new CCTContractTypeInvalidError(address, REGISTRY_MODULE_OWNER_CUSTOM, contractType) + if (!isRegistryModuleOwnerCustomVersion(version)) + throw new CCTContractVersionUnsupportedError(contractType, version, { context: { address } }) + return version +} + +/** Returns the cached `TokenAdminRegistry` {@link Interface} for `version`. */ +export function getTokenAdminRegistryInterface( + version: TokenAdminRegistryVersion = TokenAdminRegistryVersion.V1_5_0, +): Interface { + return TOKEN_ADMIN_REGISTRY_INTERFACES[version] +} + +/** Returns the cached `RegistryModuleOwnerCustom` {@link Interface} for `version`. */ +export function getRegistryModuleOwnerCustomInterface( + version: RegistryModuleOwnerCustomVersion = RegistryModuleOwnerCustomVersion.V1_6_0, +): Interface { + return REGISTRY_MODULE_OWNER_CUSTOM_INTERFACES[version] +} + +/** + * Call-typed TokenAdminRegistry handle bound to `registry` on `chain`'s provider, so the ops don't + * each re-derive one. Goes through {@link getTypedContract}, the CCT layer's single + * ethers → `ethers-abitype` cast. + */ +function tokenAdminRegistry( + chain: EVMChain, + registry: string, +): TypedContract { + return getTypedContract(chain, registry, TOKEN_ADMIN_REGISTRY_V1_5_0_ABI) +} + +/** + * A token's entry in the TokenAdminRegistry, checksummed, with zero addresses preserved rather + * than omitted — callers distinguish the registry's states by comparing against `ZeroAddress`: + * + * | `administrator` | `pendingAdministrator` | state | + * | --------------- | ---------------------- | ------------------------------------- | + * | zero | zero | not registered | + * | zero | set | registered, awaiting `acceptAdmin` | + * | set | zero | active admin | + * | set | set | active admin, `transferAdmin` pending | + */ +export type TokenAdminRegistryConfig = { + administrator: string + pendingAdministrator: string + tokenPool: string +} + +/** + * Reads a token's TAR entry through the vendored ABI. + * + * @remarks Deliberately **not** {@link EVMChain.getRegistryTokenConfig}: that helper throws + * `CCIPTokenNotConfiguredError` whenever `administrator` is the zero address, which is precisely + * the registered-but-not-yet-accepted state these ops must be able to observe and report. Reading + * `getTokenConfig` directly keeps every row of the table above reachable. + */ +export async function readTokenAdminRegistryConfig( + chain: EVMChain, + registry: string, + token: string, +): Promise { + const config = resultToObject(await tokenAdminRegistry(chain, registry).getTokenConfig(token)) + return { + administrator: getAddress(config.administrator), + pendingAdministrator: getAddress(config.pendingAdministrator), + tokenPool: getAddress(config.tokenPool), + } +} + +/** + * Whether the TAR recognises `module` as a registry module — the only on-chain question it can + * answer about one, since it exposes no way to enumerate them. + */ +export function isRegistryModule( + chain: EVMChain, + registry: string, + module: string, +): Promise { + return tokenAdminRegistry(chain, registry).isRegistryModule(module) +} diff --git a/ccip-sdk/src/cct/evm/token-admin-registry/operations/accept-admin.test.ts b/ccip-sdk/src/cct/evm/token-admin-registry/operations/accept-admin.test.ts new file mode 100644 index 000000000..abd1a89a9 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-admin-registry/operations/accept-admin.test.ts @@ -0,0 +1,357 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { ZeroAddress, getAddress, getIcapAddress, id, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import { interfaces } from '../../../../evm/const.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { AcceptAdmin } from './accept-admin.ts' + +// SENDER and OTHER carry hex letters so their checksummed and lowercase spellings differ. That +// difference is what makes the `getAddress()` normalisation in the pending-admin and wallet-binding +// comparisons observable: with all-digit fixtures both spellings are identical, so dropping the +// normalisation would pass every test while locking a legitimate admin out in production (a +// lowercase address from an indexer vs a checksummed one decoded from the chain). +const SENDER = getAddress('0x' + 'ab'.repeat(20)) +const OTHER = getAddress('0x' + 'cd'.repeat(20)) +const TOKEN = '0x' + '33'.repeat(20) +const TAR = '0x' + '44'.repeat(20) +const ADDRESS = '0x' + '55'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +// acceptAdminRole(address) selector, per the vendored ABI (spec-pinned). +const SELECTOR = id('acceptAdminRole(address)').slice(0, 10) +// 20-byte address left-padded to a 32-byte word; lowercased, since ABI encoding emits lowercase hex +// regardless of how the caller spelled the address. +const word = (addr: string) => '000000000000000000000000' + addr.slice(2).toLowerCase() + +/** Encodes a `getTokenConfig` return value against the vendored TokenAdminRegistry ABI. */ +function encodeTokenConfig(config: { + administrator?: string + pendingAdministrator?: string + tokenPool?: string +}): string { + return interfaces.TokenAdminRegistry.encodeFunctionResult('getTokenConfig', [ + [ + config.administrator ?? ZeroAddress, + config.pendingAdministrator ?? ZeroAddress, + config.tokenPool ?? ZeroAddress, + ], + ]) +} + +/** + * Fake provider whose `call` answers `getTokenConfig` with a fixed config, recording every + * `tx` it was called with so tests can assert the read hit the resolved TAR with the + * expected calldata (the read is this op's only authorization gate, so it earns its own + * assertion rather than passing implicitly whenever the config happens to come back right). + */ +function stubProvider(config: { + administrator?: string + pendingAdministrator?: string + tokenPool?: string +}) { + const calls: { to?: string; data?: string }[] = [] + return { + calls, + call: (tx: { to?: string; data?: string }) => { + calls.push(tx) + return Promise.resolve(encodeTokenConfig(config)) + }, + } +} + +/** Minimal EVMChain stub — the build path resolves the TAR, then reads `getTokenConfig` off `provider`. */ +function stubChain(overrides: Partial = {}): EVMChain { + return { + provider: stubProvider({ pendingAdministrator: SENDER }), + logger: { debug() {}, info() {}, warn() {}, error() {} }, + getTokenAdminRegistryFor: (_address: string) => Promise.resolve(TAR), + nextNonce: async () => 0, + rollbackNonce: () => {}, + ...overrides, + } as unknown as EVMChain +} + +/** Fake ethers Signer for a plain (non-deployment) tx. */ +function fakeSigner(opts: { waitError?: Error } = {}) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(SENDER), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => + opts.waitError + ? Promise.reject(opts.waitError) + : Promise.resolve({ status: 1, contractAddress: null }), + }), + } +} + +describe('AcceptAdmin (cct/evm token-admin-registry operation)', () => { + describe('generate (golden vectors)', () => { + it('encodes acceptAdminRole(token) to the discovered TAR when sender is pending', async () => { + const provider = stubProvider({ pendingAdministrator: SENDER }) + const unsigned = await new AcceptAdmin().generate( + stubChain({ provider: provider as never }), + { + tokenAddress: TOKEN, + address: ADDRESS, + sender: SENDER, + }, + ) + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + const tx = unsigned.transactions[0]! + assert.equal(tx.to, TAR) + assert.equal(tx.from, SENDER) + assert.ok(tx.data!.startsWith(SELECTOR), 'data carries the acceptAdminRole selector') + assert.equal(tx.data, SELECTOR + word(TOKEN)) + + // The read is this op's only authorization gate — assert it actually hit the resolved + // TAR with `getTokenConfig(tokenAddress)`, not just that some read returned a config + // that happened to satisfy the pending-admin check. + assert.equal(provider.calls.length, 1) + assert.equal(provider.calls[0]!.to, TAR) + assert.equal( + provider.calls[0]!.data, + interfaces.TokenAdminRegistry.encodeFunctionData('getTokenConfig', [TOKEN]), + ) + }) + + it('discovers the TAR from the given address', async () => { + let seen: string | undefined + const unsigned = await new AcceptAdmin().generate( + stubChain({ + getTokenAdminRegistryFor: (address: string) => { + seen = address + return Promise.resolve(TAR) + }, + }), + { tokenAddress: TOKEN, address: ADDRESS, sender: SENDER }, + ) + assert.equal(seen, ADDRESS) + assert.equal(unsigned.transactions[0]!.to, TAR) + }) + + it('matches a checksum-insensitive sender against the pending administrator', async () => { + // pendingAdministrator decodes checksummed off-chain; a lowercase sender must still match. + const unsigned = await new AcceptAdmin().generate( + stubChain({ provider: stubProvider({ pendingAdministrator: SENDER }) as never }), + { tokenAddress: TOKEN, address: ADDRESS, sender: SENDER.toLowerCase() }, + ) + assert.equal(unsigned.transactions[0]!.data, SELECTOR + word(TOKEN)) + }) + }) + + describe('validation', () => { + it('rejects an invalid tokenAddress before any RPC', async () => { + let called = false + await assert.rejects( + () => + new AcceptAdmin().generate( + stubChain({ + getTokenAdminRegistryFor: () => { + called = true + return Promise.resolve(TAR) + }, + }), + { tokenAddress: 'not-an-address', address: ADDRESS, sender: SENDER }, + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'acceptAdmin' && + err.context.param === 'tokenAddress', + ) + assert.equal(called, false, 'validation fails before TAR discovery') + }) + + it('rejects an invalid address', async () => { + await assert.rejects( + () => + new AcceptAdmin().generate(stubChain(), { + tokenAddress: TOKEN, + address: 'not-an-address', + sender: SENDER, + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'address', + ) + }) + + it('rejects a missing sender', async () => { + await assert.rejects( + () => new AcceptAdmin().generate(stubChain(), { tokenAddress: TOKEN, address: ADDRESS }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects an invalid sender', async () => { + await assert.rejects( + () => + new AcceptAdmin().generate(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + sender: 'not-an-address', + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects the zero address written in ICAP form as sender', async () => { + // isAddress() accepts ICAP, and this never equals ZeroAddress literally + await assert.rejects( + () => + new AcceptAdmin().generate(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + sender: getIcapAddress(ZeroAddress), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects when no administrator is pending', async () => { + await assert.rejects( + () => + new AcceptAdmin().generate( + stubChain({ + provider: stubProvider({ administrator: OTHER }) as never, // pendingAdministrator omitted -> zero + }), + { tokenAddress: TOKEN, address: ADDRESS, sender: SENDER }, + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'acceptAdmin' && + err.context.param === 'sender' && + /nothing to accept/.test(err.message), + ) + }) + + it('rejects when sender is not the pending administrator', async () => { + await assert.rejects( + () => + new AcceptAdmin().generate( + stubChain({ provider: stubProvider({ pendingAdministrator: OTHER }) as never }), + { tokenAddress: TOKEN, address: ADDRESS, sender: SENDER }, + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'sender' && + /must be the pending token administrator/.test(err.message), + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new AcceptAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + sender: SENDER, + wallet: fakeSigner(), + }) + assert.deepEqual(result, { hash: HASH }) + }) + + it('throws CCIPExecTxRevertedError when the tx reverts on-chain', async () => { + await assert.rejects( + () => + new AcceptAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + sender: SENDER, + wallet: fakeSigner({ waitError: makeError('execution reverted', 'CALL_EXCEPTION') }), + }), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && err.context.operation === 'acceptAdmin', + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => + new AcceptAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + sender: SENDER, + wallet: {}, + }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + + it('defaults sender to the executing wallet address when omitted', async () => { + // fakeSigner().getAddress() resolves to SENDER, which stubChain()'s provider also + // reports as pendingAdministrator — so an omitted `sender` must still pass the + // pending-administrator pre-check by binding to the wallet. + const result = await new AcceptAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + wallet: fakeSigner(), + }) + assert.deepEqual(result, { hash: HASH }) + }) + + it('binds a lowercase sender to a checksummed wallet address', async () => { + // The wallet-binding comparison normalises both sides with getAddress(). Without that, a + // lowercase `sender` — the shape that comes out of indexers, subgraphs and `toLowerCase()` + // pipelines — would read as a different address from the checksummed one the signer reports, + // and the legitimate pending administrator would be rejected as "not the executing wallet". + // fakeSigner() reports the checksummed SENDER. + const result = await new AcceptAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + sender: SENDER.toLowerCase(), + wallet: fakeSigner(), + }) + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a malformed sender with CCTParamsInvalidError, not a raw ethers error', async () => { + // The execute override compares addresses before the base generate()'s validate() runs, + // so it must validate first — otherwise getAddress() leaks an ethers TypeError. + await assert.rejects( + () => + new AcceptAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + sender: 'not-an-address', + wallet: fakeSigner(), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'acceptAdmin' && + err.context.param === 'sender', + ) + }) + + it('rejects a sender that does not match the executing wallet', async () => { + // Regression guard: a caller-supplied `sender` must bind to the address that actually + // signs. `fakeSigner()` resolves to SENDER, which stubChain()'s provider also reports as + // pendingAdministrator — so absent this check, `sender: OTHER` would sail through the + // pending-administrator pre-check (SENDER === SENDER) yet broadcast from a signer whose + // on-chain `msg.sender` doesn't match, reverting with `OnlyPendingAdministrator` instead + // of failing fast here. + await assert.rejects( + () => + new AcceptAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + sender: OTHER, + wallet: fakeSigner(), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'acceptAdmin' && + err.context.param === 'sender' && + /executing wallet address/.test(err.message), + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-admin-registry/operations/accept-admin.ts b/ccip-sdk/src/cct/evm/token-admin-registry/operations/accept-admin.ts new file mode 100644 index 000000000..df2899eb3 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-admin-registry/operations/accept-admin.ts @@ -0,0 +1,119 @@ +/** + * acceptAdmin — accepts a pending TokenAdminRegistry administrator role for a token. + * Second half of the two-step admin handshake: `registerAdmin` (fresh registration) or + * `transferAdmin` (existing-admin hand-off) first proposes an address as + * `pendingAdministrator`; that address then calls `acceptAdmin` to become `administrator`, + * after which `setPool` is callable. Version-independent (v1.5–v2.0 share one encoding). + * + * @packageDocumentation + */ + +import { ZeroAddress, getAddress } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { validateAddress } from '../../validate.ts' +import { getTokenAdminRegistryInterface, readTokenAdminRegistryConfig } from '../contracts.ts' + +/** + * Parameters for `acceptAdmin`. + * @remarks `sender` is typed optional to satisfy `EVMOperation`'s shared shape, but is required + * for {@link AcceptAdmin.generate}: the pre-tx check below has nothing to compare + * `pendingAdministrator` against without it, so an omitted `sender` is rejected in + * {@link AcceptAdmin.parse}. {@link AcceptAdmin.execute} relaxes this — it defaults `sender` + * to the signing wallet's own address, since that is the only address that can ever satisfy + * the pending-administrator check for a signed submission (see {@link AcceptAdmin.execute}). + */ +export type AcceptAdminParams = { + /** Token whose pending registry admin role is being accepted. */ + tokenAddress: string + /** + * Contract to resolve the TokenAdminRegistry from. Pass the registry itself for a + * direct lookup; a Router, OnRamp, OffRamp, or TokenPool also work but add hops and + * need a configured lane. + */ + address: string + /** + * Pending administrator accepting the role. Required for {@link AcceptAdmin.generate} + * (unsigned/offline flows); optional for {@link AcceptAdmin.execute}, which defaults it to + * the wallet's address — see the remarks above. + */ + sender?: string +} + +/** {@link AcceptAdminParams} as {@link AcceptAdmin.parse} leaves it: `sender` present and checksummed. */ +type ParsedAcceptAdminParams = AcceptAdminParams & { sender: string } + +/** Accepts a pending TokenAdminRegistry administrator role for a token. */ +export class AcceptAdmin extends EVMOperation { + readonly name = 'acceptAdmin' + + /** + * Validates all addresses before any RPC — `sender` is required here (unlike the base + * `EVMOperation` shape), see the {@link AcceptAdminParams} remarks — and checksums `sender` so + * {@link buildUnsigned} can compare it against the registry's own checksummed + * `pendingAdministrator` without re-asserting it. + */ + protected override parse(p: AcceptAdminParams): ParsedAcceptAdminParams { + validateAddress(this.name, 'tokenAddress', p.tokenAddress) + validateAddress(this.name, 'address', p.address) + validateAddress(this.name, 'sender', p.sender) + return { ...p, sender: getAddress(p.sender) } + } + + /** + * Confirms `sender` is the pending administrator, then builds `acceptAdminRole` calldata + * against the TokenAdminRegistry resolved from `address`. + */ + protected async buildUnsigned( + chain: EVMChain, + p: ParsedAcceptAdminParams, + ): Promise { + const to = await chain.getTokenAdminRegistryFor(p.address) + const { administrator, pendingAdministrator } = await readTokenAdminRegistryConfig( + chain, + to, + p.tokenAddress, + ) + + if (pendingAdministrator === ZeroAddress) { + throw new CCTParamsInvalidError( + this.name, + 'sender', + `no administrator is pending for this token (current administrator: ${administrator}) — nothing to accept`, + ) + } + if (pendingAdministrator !== p.sender) { + throw new CCTParamsInvalidError( + this.name, + 'sender', + `must be the pending token administrator (${pendingAdministrator})`, + ) + } + + // TAR.acceptAdminRole encoding is version-stable across v1.5–v2.0; no version dispatch needed. + const data = getTokenAdminRegistryInterface().encodeFunctionData('acceptAdminRole', [ + p.tokenAddress, + ]) + return callTx(to, data) + } + + /** + * Signs and submits as the pending administrator, defaulting `sender` to the signing wallet — + * the only address that can satisfy {@link buildUnsigned}'s pending-administrator check for a + * broadcast tx. See {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is + * rejected rather than signed. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token-admin-registry/operations/get-supported-tokens.test.ts b/ccip-sdk/src/cct/evm/token-admin-registry/operations/get-supported-tokens.test.ts new file mode 100644 index 000000000..cf44fcf60 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-admin-registry/operations/get-supported-tokens.test.ts @@ -0,0 +1,126 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { interfaces } from '../../../../evm/const.ts' +import { EVMChain } from '../../../../evm/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { GetSupportedTokens } from './get-supported-tokens.ts' + +const OFF_RAMP = '0x' + '11'.repeat(20) +const TAR = '0x' + '22'.repeat(20) +const TOKENS = ['0x' + '33'.repeat(20), '0x' + '44'.repeat(20)] + +describe('GetSupportedTokens (cct/evm)', () => { + describe('query', () => { + it('resolves the TAR and lists its configured tokens', async () => { + let resolvedAddress: string | undefined + let seenOpts: { page?: number } | undefined + const chain = { + getTokenAdminRegistryFor: async (address: string) => { + resolvedAddress = address + return TAR + }, + getSupportedTokens: async (registry: string, opts?: { page?: number }) => { + assert.equal(registry, TAR) + seenOpts = opts + return TOKENS + }, + } as unknown as EVMChain + + const result = await new GetSupportedTokens().query(chain, { address: OFF_RAMP }) + assert.deepEqual(result, TOKENS) + assert.equal(resolvedAddress, OFF_RAMP) + assert.deepEqual(seenOpts, { page: undefined }) + }) + + it('forwards `page` to chain.getSupportedTokens, which owns the pagination loop', async () => { + let seenPage: number | undefined + const chain = { + getTokenAdminRegistryFor: async () => TAR, + getSupportedTokens: async (_registry: string, opts?: { page?: number }) => { + seenPage = opts?.page + return TOKENS + }, + } as unknown as EVMChain + + await new GetSupportedTokens().query(chain, { address: OFF_RAMP, page: 50 }) + assert.equal(seenPage, 50) + }) + + it('paginates `getAllConfiguredTokens` through the real EVMChain.getSupportedTokens loop', async () => { + // The two tests above stub `chain.getSupportedTokens` wholesale, so they only pin that this + // op forwards `page` — they cannot catch a startIndex/maxCount swap or a dropped final page + // in the loop itself. This test runs the *real* `EVMChain.prototype.getSupportedTokens` + // (bound to a stub with just `provider.call`) against three tokens with `page: 2`, so a full + // first page (0,2) must be followed by a short second page (2,2) that ends the scan. + const allTokens = [...TOKENS, '0x' + '55'.repeat(20)] + const seenCalls: Array<{ startIndex: bigint; maxCount: bigint }> = [] + const chain = { + getTokenAdminRegistryFor: async () => TAR, + provider: { + call: async ({ data }: { data: string }) => { + const [startIndex, maxCount] = interfaces.TokenAdminRegistry.decodeFunctionData( + 'getAllConfiguredTokens', + data, + ) as unknown as [bigint, bigint] + seenCalls.push({ startIndex, maxCount }) + const page = allTokens.slice(Number(startIndex), Number(startIndex + maxCount)) + return interfaces.TokenAdminRegistry.encodeFunctionResult('getAllConfiguredTokens', [ + page, + ]) + }, + }, + } as unknown as EVMChain + chain.getSupportedTokens = EVMChain.prototype.getSupportedTokens.bind(chain) + + const result = await new GetSupportedTokens().query(chain, { address: OFF_RAMP, page: 2 }) + + assert.deepEqual(result, allTokens, 'pages are concatenated in order') + assert.deepEqual( + seenCalls, + [ + { startIndex: 0n, maxCount: 2n }, + { startIndex: 2n, maxCount: 2n }, + ], + 'startIndex advances by the previous page length and maxCount stays pinned to `page`', + ) + }) + }) + + describe('validation', () => { + it('rejects an invalid address before any RPC', async () => { + let called = false + const chain = { + getTokenAdminRegistryFor: async () => { + called = true + return TAR + }, + } as unknown as EVMChain + + await assert.rejects( + () => new GetSupportedTokens().query(chain, { address: 'not-an-address' }), + (error: unknown) => + error instanceof CCTParamsInvalidError && + error.context.operation === 'getSupportedTokens' && + error.context.param === 'address', + ) + assert.equal(called, false, 'validation fails before TAR discovery') + }) + + it('rejects a non-positive `page`', async () => { + await assert.rejects( + () => new GetSupportedTokens().query({} as EVMChain, { address: OFF_RAMP, page: 0 }), + (error: unknown) => + error instanceof CCTParamsInvalidError && error.context.param === 'page', + ) + }) + + it('rejects a non-integer `page`', async () => { + await assert.rejects( + () => new GetSupportedTokens().query({} as EVMChain, { address: OFF_RAMP, page: 1.5 }), + (error: unknown) => + error instanceof CCTParamsInvalidError && error.context.param === 'page', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-admin-registry/operations/get-supported-tokens.ts b/ccip-sdk/src/cct/evm/token-admin-registry/operations/get-supported-tokens.ts new file mode 100644 index 000000000..00d2418b9 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-admin-registry/operations/get-supported-tokens.ts @@ -0,0 +1,68 @@ +/** + * getSupportedTokens — lists the ERC-20 tokens configured in a TokenAdminRegistry. Version-independent + * (`getAllConfiguredTokens` is byte-identical from v1.5.0 through latest). + * + * @packageDocumentation + */ + +import type { EVMChain } from '../../../../evm/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { EVMQuery } from '../../query.ts' +import { validateAddress } from '../../validate.ts' + +/** Parameters for {@link GetSupportedTokens}. */ +export type GetSupportedTokensParams = { + /** + * Contract to resolve the TokenAdminRegistry from. Pass the registry itself for a + * direct lookup; a Router, OnRamp, OffRamp, or TokenPool also work but add hops and + * need a configured lane. + */ + address: string + /** + * Batch size `chain.getSupportedTokens` requests per `getAllConfiguredTokens` call while it + * paginates. Defaults to 1000. Optional — a very large registry may need a smaller batch to + * stay under an RPC's response-size limit. + */ + page?: number +} + +/** Result of {@link GetSupportedTokens}: array of token addresses. */ +export type GetSupportedTokensResult = string[] + +/** + * Lists every token configured in the TokenAdminRegistry resolved from `address`, paginating + * through `getAllConfiguredTokens` until exhausted. + */ +export class GetSupportedTokens extends EVMQuery { + readonly name = 'getSupportedTokens' + + /** + * Validates the resolution address and, when given, `page`; nothing to convert for {@link read}. + * @throws {@link CCTParamsInvalidError} if `address` is not a valid address, or `page` is given + * and is not a positive integer + */ + protected prepare(params: GetSupportedTokensParams): GetSupportedTokensParams { + validateAddress(this.name, 'address', params.address) + if (params.page !== undefined && !(Number.isInteger(params.page) && params.page > 0)) + throw new CCTParamsInvalidError( + this.name, + 'page', + `must be a positive integer, got ${String(params.page)}`, + ) + return params + } + + /** + * Resolves the TAR and lists its configured tokens. + * @remarks Delegates pagination to {@link EVMChain.getSupportedTokens}, which already loops + * `getAllConfiguredTokens(startIndex, maxCount)` until a short page ends the scan — this op does + * not reimplement that loop. + */ + protected async read( + chain: EVMChain, + { address, page }: GetSupportedTokensParams, + ): Promise { + const registry = await chain.getTokenAdminRegistryFor(address) + return chain.getSupportedTokens(registry, { page }) + } +} diff --git a/ccip-sdk/src/cct/evm/token-admin-registry/operations/get-token-admin-registry.test.ts b/ccip-sdk/src/cct/evm/token-admin-registry/operations/get-token-admin-registry.test.ts new file mode 100644 index 000000000..0e2ac1078 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-admin-registry/operations/get-token-admin-registry.test.ts @@ -0,0 +1,181 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, getAddress, makeError } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { GetTokenAdminRegistry } from './get-token-admin-registry.ts' + +const TOKEN = '0x' + '11'.repeat(20) +const ROUTER = '0x' + '22'.repeat(20) +const TAR = '0x' + '33'.repeat(20) +// Mixed-case hex, unlike TOKEN/ROUTER/TAR above: their EIP-55 checksums re-case letters, so +// asserting `getAddress(FIXTURE)` below only proves normalization happens if the raw fixture +// isn't already in checksummed form. A digit-only fixture would pass even if the op forgot to +// checksum (or lowercased) its output. +const ADMINISTRATOR = '0xabcdef1234567890abcdef1234567890abcdef12' +const PENDING_ADMINISTRATOR = '0x1234567890abcdef1234567890abcdef12345678' +const POOL = '0xfedcba9876543210fedcba9876543210fedcba98' + +const IFACE = new Interface([ + 'function getTokenConfig(address token) view returns (tuple(address administrator, address pendingAdministrator, address tokenPool))', +]) + +/** + * EVMChain stub: `getTokenAdminRegistryFor` reports `registry`, and the provider answers + * `eth_call` with `getTokenConfig` encoded as `(administrator, pendingAdministrator, tokenPool)` — + * but only for a call to `registry` decoding to `TOKEN`; any other call, target, or argument + * reverts (or fails the assertion), so a read that mixes up its target or argument is caught + * rather than silently returning the fixture data. + */ +function stubChain({ + registry = TAR, + administrator = ADMINISTRATOR, + pendingAdministrator = PENDING_ADMINISTRATOR, + tokenPool = POOL, +}: { + registry?: string + administrator?: string + pendingAdministrator?: string + tokenPool?: string +} = {}): EVMChain { + const encoded = IFACE.encodeFunctionResult('getTokenConfig', [ + [administrator, pendingAdministrator, tokenPool], + ]) + const selector = IFACE.getFunction('getTokenConfig')!.selector + + return { + provider: { + call: async ({ to, data }: { to?: string; data: string }) => { + if (data.slice(0, 10) !== selector) + throw makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: null, data }, + invocation: null, + revert: null, + }) + // Selector-only matching can't tell a correct read from one with the call target or the + // decoded token argument swapped (e.g. reading a different contract's, or a different + // token's, config) — both would still hit this branch and get `encoded` back. Assert the + // resolved registry and the decoded argument so either mix-up fails loudly instead of + // silently returning the fixture data. + assert.equal(to, getAddress(registry), 'calls the resolved TAR, not `address`') + const [token] = IFACE.decodeFunctionData('getTokenConfig', data) + assert.equal(token, getAddress(TOKEN), 'reads the config for `tokenAddress`') + return encoded + }, + }, + logger: { debug() {}, info() {}, warn() {}, error() {} }, + getTokenAdminRegistryFor: () => Promise.resolve(registry), + } as unknown as EVMChain +} + +describe('GetTokenAdminRegistry (cct/evm token-admin-registry query)', () => { + it('reads administrator, pendingAdministrator, and tokenPool', async () => { + const config = await new GetTokenAdminRegistry().query(stubChain(), { + address: ROUTER, + tokenAddress: TOKEN, + }) + + assert.deepEqual(config, { + administrator: getAddress(ADMINISTRATOR), + pendingAdministrator: getAddress(PENDING_ADMINISTRATOR), + tokenPool: getAddress(POOL), + }) + }) + + it('resolves the TAR from `address` before reading', async () => { + let seen: string | undefined + const chain = stubChain() + chain.getTokenAdminRegistryFor = (address: string) => { + seen = address + return Promise.resolve(TAR) + } + + await new GetTokenAdminRegistry().query(chain, { address: ROUTER, tokenAddress: TOKEN }) + + assert.equal(seen, ROUTER) + }) + + it( + 'reports a zero administrator rather than throwing — the pending-registration state ' + + 'EVMChain.getRegistryTokenConfig cannot observe', + async () => { + const config = await new GetTokenAdminRegistry().query( + stubChain({ administrator: ZeroAddress }), + { address: ROUTER, tokenAddress: TOKEN }, + ) + + assert.equal(config.administrator, ZeroAddress) + assert.equal(config.pendingAdministrator, getAddress(PENDING_ADMINISTRATOR)) + }, + ) + + it('omits pendingAdministrator when zero', async () => { + const config = await new GetTokenAdminRegistry().query( + stubChain({ pendingAdministrator: ZeroAddress }), + { address: ROUTER, tokenAddress: TOKEN }, + ) + + assert.ok(!('pendingAdministrator' in config)) + }) + + it('omits tokenPool when zero', async () => { + const config = await new GetTokenAdminRegistry().query(stubChain({ tokenPool: ZeroAddress }), { + address: ROUTER, + tokenAddress: TOKEN, + }) + + assert.ok(!('tokenPool' in config)) + }) + + it('omits both optional fields for an unregistered token', async () => { + const config = await new GetTokenAdminRegistry().query( + stubChain({ + administrator: ZeroAddress, + pendingAdministrator: ZeroAddress, + tokenPool: ZeroAddress, + }), + { address: ROUTER, tokenAddress: TOKEN }, + ) + + assert.deepEqual(config, { administrator: ZeroAddress }) + }) + + describe('validation', () => { + it('rejects an invalid `address` before any RPC', async () => { + let called = false + const chain = stubChain() + chain.getTokenAdminRegistryFor = () => { + called = true + return Promise.resolve(TAR) + } + + await assert.rejects( + () => new GetTokenAdminRegistry().query(chain, { address: 'nope', tokenAddress: TOKEN }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'getTokenAdminRegistry' && + err.context.param === 'address', + ) + assert.equal(called, false, 'validation fails before TAR discovery') + }) + + it('rejects an invalid `tokenAddress` before any RPC', async () => { + await assert.rejects( + () => + new GetTokenAdminRegistry().query(stubChain(), { + address: ROUTER, + tokenAddress: 'nope', + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'getTokenAdminRegistry' && + err.context.param === 'tokenAddress', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-admin-registry/operations/get-token-admin-registry.ts b/ccip-sdk/src/cct/evm/token-admin-registry/operations/get-token-admin-registry.ts new file mode 100644 index 000000000..06344480a --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-admin-registry/operations/get-token-admin-registry.ts @@ -0,0 +1,84 @@ +/** + * getTokenAdminRegistry — reads a token's TokenAdminRegistry entry: its administrator, any + * pending administrator, and its registered pool. Version-independent (v1.5–v2.0 share one + * `getTokenConfig` encoding). + * + * @packageDocumentation + */ + +import { ZeroAddress } from 'ethers' + +import type { RegistryTokenConfig } from '../../../../chain.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { EVMQuery } from '../../query.ts' +import { validateAddress } from '../../validate.ts' +import { readTokenAdminRegistryConfig } from '../contracts.ts' + +/** Parameters for {@link GetTokenAdminRegistry}. */ +export type GetTokenAdminRegistryParams = { + /** + * Contract to resolve the TokenAdminRegistry from. Pass the registry itself for a + * direct lookup; a Router, OnRamp, OffRamp, or TokenPool also work but add hops and + * need a configured lane. + */ + address: string + /** Token to read the registry entry for. */ + tokenAddress: string +} + +/** + * Result of {@link GetTokenAdminRegistry}: the TAR entry for one token. + * @remarks `administrator` may be {@link ZeroAddress} for a token pending acceptance; + * test with `=== ZeroAddress`, not truthiness. + */ +export type GetTokenAdminRegistryResult = RegistryTokenConfig + +/** + * Reads a token's TokenAdminRegistry entry directly through `getTokenConfig`, reporting a zero + * `administrator` rather than throwing. + * @remarks Deliberately diverges from `EVMChain.getRegistryTokenConfig`, which throws + * `CCIPTokenNotConfiguredError` whenever `administrator === ZeroAddress` — exactly the + * post-`registerAdmin`, pre-`acceptAdmin` state, which made a pending registration + * unobservable through the public read API. This op reads `getTokenConfig` through + * {@link readTokenAdminRegistryConfig} instead of delegating to that helper, so + * `{ administrator: ZeroAddress, pendingAdministrator }` is reported faithfully. + * `pendingAdministrator` and `tokenPool` are still omitted when zero (nothing pending, no pool + * registered) — only `administrator` survives as the zero address, since that is the one state + * this op exists to surface. + */ +export class GetTokenAdminRegistry extends EVMQuery< + GetTokenAdminRegistryParams, + GetTokenAdminRegistryResult +> { + readonly name = 'getTokenAdminRegistry' + + /** + * Validates both addresses; nothing to convert for {@link read}. + * @throws {@link CCTParamsInvalidError} if `address` or `tokenAddress` is not a valid address + */ + protected prepare(params: GetTokenAdminRegistryParams): GetTokenAdminRegistryParams { + validateAddress(this.name, 'address', params.address) + validateAddress(this.name, 'tokenAddress', params.tokenAddress) + return params + } + + /** Resolves the TAR from `address`, then reads and normalizes `getTokenConfig(tokenAddress)`. */ + protected async read( + chain: EVMChain, + { address, tokenAddress }: GetTokenAdminRegistryParams, + ): Promise { + const registry = await chain.getTokenAdminRegistryFor(address) + const config = await readTokenAdminRegistryConfig(chain, registry, tokenAddress) + + return { + // unlike EVMChain.getRegistryTokenConfig, a zero administrator is reported, not thrown — + // that's the whole point of this op (see the class @remarks). The two optional fields are + // dropped when zero, so callers can test presence rather than compare against ZeroAddress. + administrator: config.administrator, + ...(config.pendingAdministrator !== ZeroAddress && { + pendingAdministrator: config.pendingAdministrator, + }), + ...(config.tokenPool !== ZeroAddress && { tokenPool: config.tokenPool }), + } + } +} diff --git a/ccip-sdk/src/cct/evm/token-admin-registry/operations/register-admin.test.ts b/ccip-sdk/src/cct/evm/token-admin-registry/operations/register-admin.test.ts new file mode 100644 index 000000000..497592092 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-admin-registry/operations/register-admin.test.ts @@ -0,0 +1,656 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, getAddress, id, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import { interfaces } from '../../../../evm/const.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { + CCTContractTypeInvalidError, + CCTContractVersionUnsupportedError, + CCTParamsInvalidError, +} from '../../../errors.ts' +import { RegisterAdmin } from './register-admin.ts' + +const TOKEN = '0x' + '11'.repeat(20) +const REGISTRY_MODULE = '0x' + '22'.repeat(20) +const ROUTER = '0x' + '33'.repeat(20) +const TAR = '0x' + '44'.repeat(20) +// Deliberately letter-bearing, so its checksummed and lowercase spellings differ. The +// already-registered assertions below feed the stub a lowercase administrator and assert the +// error carries the checksummed form — which is what pins `readTokenAdminRegistryConfig`'s +// checksumming. With an all-digit fixture the two spellings coincide and that guarantee is +// silently untested. +const ADMIN = getAddress('0x' + 'ad'.repeat(20)) +const OTHER = '0x' + '66'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) +const ROLE = '0x' + '00'.repeat(32) // OZ's DEFAULT_ADMIN_ROLE constant (bytes32(0)) + +// Module-call golden vectors, written by hand against a locally-declared Interface rather than +// the vendored REGISTRY_MODULE_OWNER_CUSTOM_ABI, so this stays an independent check: swapping the +// encoded argument (e.g. registryModule instead of token) or the wrong moduleFn would show up here +// even though it'd also validate cleanly against the (correct) vendored ABI. +const GOLDEN_MODULE_INTERFACE = new Interface([ + 'function registerAdminViaOwner(address token)', + 'function registerAdminViaGetCCIPAdmin(address token)', + 'function registerAccessControlDefaultAdmin(address token)', +]) +const OWNER_DATA = GOLDEN_MODULE_INTERFACE.encodeFunctionData('registerAdminViaOwner', [TOKEN]) +const CCIP_ADMIN_DATA = GOLDEN_MODULE_INTERFACE.encodeFunctionData('registerAdminViaGetCCIPAdmin', [ + TOKEN, +]) +const ACCESS_CONTROL_DATA = GOLDEN_MODULE_INTERFACE.encodeFunctionData( + 'registerAccessControlDefaultAdmin', + [TOKEN], +) + +// TAR-side selectors probed by pre-tx validation. +const IS_REGISTRY_MODULE_SELECTOR = + interfaces.TokenAdminRegistry.getFunction('isRegistryModule')!.selector +const GET_TOKEN_CONFIG_SELECTOR = + interfaces.TokenAdminRegistry.getFunction('getTokenConfig')!.selector + +// Token-side selectors, independently derived via `id(...)` rather than read back from the +// throwaway Interfaces the op itself builds — so a wrong-getter mutation in the op can't also +// silently rewrite the expectation. +const OWNER_GETTER_SELECTOR = id('owner()').slice(0, 10) +const CCIP_ADMIN_GETTER_SELECTOR = id('getCCIPAdmin()').slice(0, 10) +const DEFAULT_ADMIN_ROLE_SELECTOR = id('DEFAULT_ADMIN_ROLE()').slice(0, 10) +const HAS_ROLE_SELECTOR = id('hasRole(bytes32,address)').slice(0, 10) + +/** Throwaway single-fragment interface for a token getter, mirroring the op's own probe. */ +const getterInterface = (name: string) => + new Interface([`function ${name}() view returns (address)`]) +/** Mirrors the op's own throwaway AccessControl interface, used to decode recorded calls. */ +const accessControlInterface = new Interface([ + 'function DEFAULT_ADMIN_ROLE() view returns (bytes32)', + 'function hasRole(bytes32, address) view returns (bool)', +]) + +type TokenConfig = { administrator: string; pendingAdministrator: string; tokenPool: string } + +const UNREGISTERED: TokenConfig = { + administrator: ZeroAddress, + pendingAdministrator: ZeroAddress, + tokenPool: ZeroAddress, +} + +/** A recorded `provider.call`: its target and raw calldata, for asserting *where* a probe went. */ +type RecordedCall = { to: string | undefined; data: string } + +/** + * Minimal EVMChain stub with a selector-aware `provider.call`, mirroring `finality-preflight.test.ts`. + * Pass `calls` to record every call `{ to, data }` — needed because the per-method getter mapping + * is otherwise untestable: `encodeFunctionResult` for a `() view returns (address)` fragment + * produces identical bytes regardless of the function name, so only the *request* (selector + + * target), not the stubbed response, can prove which getter was actually probed. Any selector this + * stub doesn't recognise throws (rather than falling back to a generic response), so a probe aimed + * at the wrong function or the wrong address fails loudly instead of returning a plausible value. + */ +function stubChain( + opts: { + isModule?: boolean + tokenConfig?: TokenConfig + getter?: string + getterAddress?: string + hasRole?: boolean + moduleTypeAndVersion?: [string, string] + calls?: RecordedCall[] + overrides?: Partial + } = {}, +): EVMChain { + const isModule = opts.isModule ?? true + const tokenConfig = opts.tokenConfig ?? UNREGISTERED + const getter = opts.getter ?? 'owner' + const getterAddress = opts.getterAddress ?? ADMIN + const hasRole = opts.hasRole ?? true + const [moduleType, moduleVersion] = opts.moduleTypeAndVersion ?? [ + 'RegistryModuleOwnerCustom', + '1.6.0', + ] + + const provider = { + call: async (tx: { to?: string; data?: string }) => { + const data = tx.data ?? '0x' + const sel = data.slice(0, 10) + opts.calls?.push({ to: tx.to, data }) + // Recording alone leaves the TAR-side probes unpinned unless a test bothers to inspect + // `calls`. Asserting here instead pins them for EVERY test: without this, swapping an + // argument (e.g. `getTokenConfig(registryModule)`, which silently disables the + // already-registered guard) or aiming a probe at the wrong contract keeps the suite green. + const at = (label: string, expected: string) => + assert.equal(getAddress(tx.to ?? ZeroAddress), getAddress(expected), `${label} target`) + if (sel === IS_REGISTRY_MODULE_SELECTOR) { + at('isRegistryModule', TAR) + const [mod] = interfaces.TokenAdminRegistry.decodeFunctionData( + 'isRegistryModule', + data, + ) as unknown as [string] + assert.equal( + getAddress(mod), + getAddress(REGISTRY_MODULE), + 'isRegistryModule asks about `registryModule`', + ) + return interfaces.TokenAdminRegistry.encodeFunctionResult('isRegistryModule', [isModule]) + } + if (sel === GET_TOKEN_CONFIG_SELECTOR) { + at('getTokenConfig', TAR) + const [tok] = interfaces.TokenAdminRegistry.decodeFunctionData( + 'getTokenConfig', + data, + ) as unknown as [string] + assert.equal(getAddress(tok), getAddress(TOKEN), 'getTokenConfig asks about `tokenAddress`') + return interfaces.TokenAdminRegistry.encodeFunctionResult('getTokenConfig', [ + [tokenConfig.administrator, tokenConfig.pendingAdministrator, tokenConfig.tokenPool], + ]) + } + // Every token-side probe must read the token itself, never the module or the registry. + if (sel === DEFAULT_ADMIN_ROLE_SELECTOR) { + at('DEFAULT_ADMIN_ROLE', TOKEN) + return accessControlInterface.encodeFunctionResult('DEFAULT_ADMIN_ROLE', [ROLE]) + } + if (sel === HAS_ROLE_SELECTOR) { + at('hasRole', TOKEN) + return accessControlInterface.encodeFunctionResult('hasRole', [hasRole]) + } + if (sel === getterInterface(getter).getFunction(getter)!.selector) { + at(`${getter}()`, TOKEN) + return getterInterface(getter).encodeFunctionResult(getter, [getterAddress]) + } + throw new Error(`stubChain: unrecognised selector ${sel} at ${tx.to ?? '(no to)'}`) + }, + } + + return { + provider, + logger: { debug() {}, info() {}, warn() {}, error() {} }, + getTokenAdminRegistryFor: (_address: string) => Promise.resolve(TAR), + typeAndVersion: (_address: string) => + Promise.resolve([moduleType, moduleVersion, `${moduleType} ${moduleVersion}`]), + nextNonce: async () => 0, + rollbackNonce: () => {}, + ...opts.overrides, + } as unknown as EVMChain +} + +/** Fake ethers Signer for a plain (non-deployment) tx. */ +function fakeSigner(opts: { address?: string; waitError?: Error } = {}) { + const address = opts.address ?? ADMIN + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => + opts.waitError + ? Promise.reject(opts.waitError) + : Promise.resolve({ status: 1, contractAddress: null }), + }), + } +} + +describe('RegisterAdmin (cct/evm token-admin-registry operation)', () => { + describe('generate (golden vectors)', () => { + it('defaults to owner and encodes registerAdminViaOwner(token) to the module', async () => { + const unsigned = await new RegisterAdmin().generate(stubChain(), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + sender: ADMIN, + }) + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + const tx = unsigned.transactions[0]! + assert.equal(tx.to, REGISTRY_MODULE) + assert.equal(tx.from, ADMIN) + // Full calldata, not just the selector — catches a wrong-argument encode (e.g. the + // registryModule address instead of the token) that `startsWith(selector)` would miss. + assert.equal(tx.data, OWNER_DATA) + }) + + it('encodes registerAdminViaGetCCIPAdmin(token) for ccip-admin', async () => { + const unsigned = await new RegisterAdmin().generate(stubChain({ getter: 'getCCIPAdmin' }), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + registrationMethod: 'ccip-admin', + sender: ADMIN, + }) + assert.equal(unsigned.transactions[0]!.data, CCIP_ADMIN_DATA) + }) + + it('encodes registerAccessControlDefaultAdmin(token) for access-control-default-admin', async () => { + const unsigned = await new RegisterAdmin().generate(stubChain(), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + registrationMethod: 'access-control-default-admin', + sender: ADMIN, + }) + assert.equal(unsigned.transactions[0]!.data, ACCESS_CONTROL_DATA) + }) + + it('discovers the TAR from the router address', async () => { + let seen: string | undefined + const unsigned = await new RegisterAdmin().generate( + stubChain({ + overrides: { + getTokenAdminRegistryFor: (address: string) => { + seen = address + return Promise.resolve(TAR) + }, + }, + }), + { tokenAddress: TOKEN, registryModule: REGISTRY_MODULE, address: ROUTER }, + ) + assert.equal(seen, ROUTER) + assert.ok(unsigned) + }) + + it('omits `from` when no sender is given (and skips the getter probe)', async () => { + const unsigned = await new RegisterAdmin().generate(stubChain(), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + }) + assert.equal(unsigned.transactions[0]!.from, undefined) + }) + }) + + describe('per-method token-side probe', () => { + // These assert the *request* (selector + target), not just a stubbed return value, since + // `stubChain`'s per-method responses are otherwise indistinguishable (see its doc comment). + // This is the coverage that would have caught the `access-control-default-admin` blocker: a + // probe against the wrong selector (`defaultAdmin()`) shows up directly instead of being + // absorbed by a catch-all stub response. + + it('probes owner() at the token (not the module) for the default method', async () => { + const calls: RecordedCall[] = [] + await new RegisterAdmin().generate(stubChain({ calls }), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + sender: ADMIN, + }) + const probe = calls.find((c) => c.data.slice(0, 10) === OWNER_GETTER_SELECTOR) + assert.ok(probe, 'owner() was probed') + assert.equal(probe.to, TOKEN) + }) + + it('probes getCCIPAdmin() at the token for ccip-admin', async () => { + const calls: RecordedCall[] = [] + await new RegisterAdmin().generate(stubChain({ getter: 'getCCIPAdmin', calls }), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + registrationMethod: 'ccip-admin', + sender: ADMIN, + }) + const probe = calls.find((c) => c.data.slice(0, 10) === CCIP_ADMIN_GETTER_SELECTOR) + assert.ok(probe, 'getCCIPAdmin() was probed') + assert.equal(probe.to, TOKEN) + }) + + it('probes DEFAULT_ADMIN_ROLE()/hasRole(role, sender) at the token for access-control-default-admin', async () => { + const calls: RecordedCall[] = [] + await new RegisterAdmin().generate(stubChain({ calls }), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + registrationMethod: 'access-control-default-admin', + sender: ADMIN, + }) + + const roleCall = calls.find((c) => c.data.slice(0, 10) === DEFAULT_ADMIN_ROLE_SELECTOR) + assert.ok(roleCall, 'DEFAULT_ADMIN_ROLE() was probed') + assert.equal(roleCall.to, TOKEN) + + const hasRoleCall = calls.find((c) => c.data.slice(0, 10) === HAS_ROLE_SELECTOR) + assert.ok(hasRoleCall, 'hasRole(role, sender) was probed') + assert.equal(hasRoleCall.to, TOKEN) + const [role, account] = accessControlInterface.decodeFunctionData('hasRole', hasRoleCall.data) + assert.equal(role, ROLE) + assert.equal(account, ADMIN) + }) + }) + + describe('validation', () => { + it('rejects an invalid tokenAddress before any RPC', async () => { + let called = false + await assert.rejects( + () => + new RegisterAdmin().generate( + stubChain({ + overrides: { + getTokenAdminRegistryFor: () => ((called = true), Promise.resolve(TAR)), + }, + }), + { tokenAddress: 'nope', registryModule: REGISTRY_MODULE, address: ROUTER }, + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'registerAdmin' && + err.context.param === 'tokenAddress', + ) + assert.equal(called, false, 'validation fails before TAR discovery') + }) + + it('rejects an invalid registryModule', async () => { + await assert.rejects( + () => + new RegisterAdmin().generate(stubChain(), { + tokenAddress: TOKEN, + registryModule: 'nope', + address: ROUTER, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'registryModule', + ) + }) + + it('rejects an invalid address', async () => { + await assert.rejects( + () => + new RegisterAdmin().generate(stubChain(), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: 'nope', + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'address', + ) + }) + + it('rejects an unrecognised registrationMethod', async () => { + await assert.rejects( + () => + new RegisterAdmin().generate(stubChain(), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + registrationMethod: 'nope' as never, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'registrationMethod', + ) + }) + + it('rejects a registryModule the TAR does not recognise', async () => { + await assert.rejects( + () => + new RegisterAdmin().generate(stubChain({ isModule: false }), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'registerAdmin' && + err.context.param === 'registryModule', + ) + }) + + it('rejects a declared version that disagrees with the module on-chain', async () => { + // `registryModuleVersion` defaults to 1.6.0, so this declares 1.6.0 against a 1.5.0 module. + // Both versions encode the shared functions identically, so nothing downstream would notice — + // the resolved version is what makes the compile-time narrowing true. + await assert.rejects( + () => + new RegisterAdmin().generate( + stubChain({ moduleTypeAndVersion: ['RegistryModuleOwnerCustom', '1.5.0'] }), + { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + registrationMethod: 'access-control-default-admin', + }, + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'registryModuleVersion' && + typeof err.context.reason === 'string' && + err.context.reason.includes('v1.5.0'), + ) + }) + + it('accepts a v1.5.0 module when that version is declared', async () => { + // The union removes `access-control-default-admin` from `registrationMethod` here, so only + // the two getter-derived paths are even expressible. + const unsigned = await new RegisterAdmin().generate( + stubChain({ moduleTypeAndVersion: ['RegistryModuleOwnerCustom', '1.5.0'] }), + { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + registryModuleVersion: '1.5.0', + sender: ADMIN, + }, + ) + assert.equal(unsigned.transactions[0]!.data, OWNER_DATA) + }) + + it('rejects an address that is not a RegistryModuleOwnerCustom', async () => { + await assert.rejects( + () => + new RegisterAdmin().generate( + stubChain({ moduleTypeAndVersion: ['TokenPool', '1.6.0'] }), + { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + }, + ), + (err: unknown) => err instanceof CCTContractTypeInvalidError, + ) + }) + + it('rejects a module reporting an unknown version', async () => { + await assert.rejects( + () => + new RegisterAdmin().generate( + stubChain({ moduleTypeAndVersion: ['RegistryModuleOwnerCustom', '9.9.9'] }), + { tokenAddress: TOKEN, registryModule: REGISTRY_MODULE, address: ROUTER }, + ), + (err: unknown) => err instanceof CCTContractVersionUnsupportedError, + ) + }) + + it('rejects when sender does not match the token getter for the method', async () => { + await assert.rejects( + () => + new RegisterAdmin().generate(stubChain({ getterAddress: OTHER }), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + sender: ADMIN, + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects when sender lacks DEFAULT_ADMIN_ROLE for access-control-default-admin', async () => { + await assert.rejects( + () => + new RegisterAdmin().generate(stubChain({ hasRole: false }), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + registrationMethod: 'access-control-default-admin', + sender: ADMIN, + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects a token already registered (administrator set)', async () => { + await assert.rejects( + () => + new RegisterAdmin().generate( + stubChain({ + tokenConfig: { + administrator: ADMIN.toLowerCase(), + pendingAdministrator: ZeroAddress, + tokenPool: ZeroAddress, + }, + }), + { tokenAddress: TOKEN, registryModule: REGISTRY_MODULE, address: ROUTER }, + ), + // The two already-registered cases carry different remediation (hand the role over vs + // wait for the pending admin to accept), so each pins its own message — asserting only + // `param` would let the branches be swapped without any test noticing. + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'tokenAddress' && + typeof err.context.reason === 'string' && + err.context.reason.includes('already has registry administrator') && + err.context.reason.includes(ADMIN), + ) + }) + + it('rejects a token with a pending registration (administrator still zero)', async () => { + await assert.rejects( + () => + new RegisterAdmin().generate( + stubChain({ + tokenConfig: { + administrator: ZeroAddress, + pendingAdministrator: ADMIN.toLowerCase(), + tokenPool: ZeroAddress, + }, + }), + { tokenAddress: TOKEN, registryModule: REGISTRY_MODULE, address: ROUTER }, + ), + // Stricter than the contract on purpose: proposeAdministrator would silently overwrite a + // pending proposal (it only reverts once `administrator` is non-zero), so this guard is + // the SDK's, and its message must name the address waiting to accept. + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'tokenAddress' && + typeof err.context.reason === 'string' && + err.context.reason.includes('already pending') && + err.context.reason.includes(ADMIN), + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new RegisterAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + wallet: fakeSigner(), + }) + assert.deepEqual(result, { hash: HASH }) + }) + + it('throws CCIPExecTxRevertedError when the tx reverts on-chain', async () => { + await assert.rejects( + () => + new RegisterAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + wallet: fakeSigner({ waitError: makeError('execution reverted', 'CALL_EXCEPTION') }), + }), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && err.context.operation === 'registerAdmin', + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => + new RegisterAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + wallet: {}, + }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + + it('defaults sender to the wallet address, rejecting a wallet that is not the token owner', async () => { + // The default `execute({ ...params, wallet })` shape — no explicit `sender` — is exactly + // the path that must not skip the authority check (see `RegisterAdmin.execute`'s doc + // comment). `stubChain()`'s owner() resolves to ADMIN; this wallet is OTHER. + await assert.rejects( + () => + new RegisterAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + wallet: fakeSigner({ address: OTHER }), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'registerAdmin' && + err.context.param === 'sender', + ) + }) + + it('rejects a sender that differs from the signing wallet', async () => { + // Uniform with transferAdmin/acceptAdmin: the module gates on the wallet's msg.sender, so + // honouring a divergent `sender` would reduce the authority pre-check to advice — the call + // would pass every local guard and still revert on-chain. Offline/multisig signers use + // generateUnsignedRegisterAdmin, where `sender` is trusted as given. + await assert.rejects( + () => + new RegisterAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + sender: ADMIN, + wallet: fakeSigner({ address: OTHER }), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'registerAdmin' && + err.context.param === 'sender' && + // pins the builder name resolveWalletSender derives from `this.name`, so the shared + // helper can't start telling registerAdmin callers to use some other method + typeof err.context.reason === 'string' && + err.context.reason.includes('generateUnsignedRegisterAdmin'), + ) + }) + + it('rejects a malformed sender with CCTParamsInvalidError, not a raw ethers error', async () => { + // resolveWalletSender validates before getAddress(), which would otherwise throw a raw + // ethers TypeError. That guard runs ahead of generate()'s own validate(), so nothing else + // covers it — without this test, deleting it leaves the suite green and silently breaks the + // documented error taxonomy for every op sharing the helper. + await assert.rejects( + () => + new RegisterAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + sender: 'not-an-address', + wallet: fakeSigner({ address: ADMIN }), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'registerAdmin' && + err.context.param === 'sender', + ) + }) + + it('accepts a sender matching the signing wallet', async () => { + // The redundant-but-explicit call shape: passing `sender` equal to the wallet is allowed, so + // callers who thread `sender` through both builders and executors need no special-casing. + const result = await new RegisterAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + registryModule: REGISTRY_MODULE, + address: ROUTER, + sender: ADMIN, + wallet: fakeSigner({ address: ADMIN }), + }) + assert.deepEqual(result, { hash: HASH }) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-admin-registry/operations/register-admin.ts b/ccip-sdk/src/cct/evm/token-admin-registry/operations/register-admin.ts new file mode 100644 index 000000000..3b840c720 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-admin-registry/operations/register-admin.ts @@ -0,0 +1,259 @@ +/** + * registerAdmin — proposes a token's administrator in the TokenAdminRegistry (TAR) by calling a + * RegistryModuleOwnerCustom, one of three self-service paths CCIP ships so a token owner never + * needs the TAR owner's help to onboard. Two-step by design, like `transferAdmin`: the token + * lands in `pendingAdministrator` until the proposed administrator calls `acceptAdmin`. + * + * @packageDocumentation + */ + +import { Contract, Interface, ZeroAddress, getAddress } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { validateAddress } from '../../validate.ts' +import { + RegistryModuleOwnerCustomVersion, + getRegistryModuleOwnerCustomInterface, + isRegistryModule, + readTokenAdminRegistryConfig, + resolveRegistryModuleOwnerCustom, +} from '../contracts.ts' + +/** + * Self-service authorization paths a RegistryModuleOwnerCustom accepts, each proving control of + * the token through a different on-chain getter rather than a signature the module has to verify + * itself. Defaults to `owner`, the common case for a plain `Ownable` token. + */ +const REGISTRATION_METHODS = { + OWNER: 'owner', + CCIP_ADMIN: 'ccip-admin', + ACCESS_CONTROL_DEFAULT_ADMIN: 'access-control-default-admin', +} as const + +/** Authorization path used to register a token's administrator via a RegistryModuleOwnerCustom. */ +export type RegisterAdminMethod = (typeof REGISTRATION_METHODS)[keyof typeof REGISTRATION_METHODS] + +/** + * Per-method wiring: the RegistryModuleOwnerCustom function this op calls. `owner`/`ccip-admin` + * also carry the token getter whose return value the module registers as administrator and + * checks against the caller (`_registerAdmin`'s `admin != msg.sender` revert) — used here to + * pre-flight that same equality. `access-control-default-admin` has no such getter: unlike the + * other two, `registerAccessControlDefaultAdmin` never derives an address from the token at all — + * it checks `AccessControl(token).hasRole(DEFAULT_ADMIN_ROLE(), msg.sender)` and then registers + * `msg.sender` itself, so it's pre-flighted as a role check in {@link RegisterAdmin.buildUnsigned} + * rather than through a `tokenGetter` here. + */ +const REGISTRATION: Record< + RegisterAdminMethod, + { readonly moduleFn: string; readonly tokenGetter?: string } +> = { + [REGISTRATION_METHODS.OWNER]: { moduleFn: 'registerAdminViaOwner', tokenGetter: 'owner' }, + [REGISTRATION_METHODS.CCIP_ADMIN]: { + moduleFn: 'registerAdminViaGetCCIPAdmin', + tokenGetter: 'getCCIPAdmin', + }, + [REGISTRATION_METHODS.ACCESS_CONTROL_DEFAULT_ADMIN]: { + moduleFn: 'registerAccessControlDefaultAdmin', + }, +} + +/** Registration paths a v1.5.0 module offers — both derive the administrator from a token getter. */ +export type RegisterAdminMethodV1_5_0 = Exclude + +/** Fields every registration path needs, whatever the module version. */ +type RegisterAdminBaseParams = { + /** Token to register. Stays unregistered until `acceptAdmin` is called by the proposed admin. */ + tokenAddress: string + /** + * `RegistryModuleOwnerCustom` to call. The TAR exposes `isRegistryModule` but no enumeration, + * so — unlike `address` below — this can't be discovered on-chain and must be supplied. + */ + registryModule: string + /** + * Contract to resolve the TokenAdminRegistry from. Pass the registry itself for a direct + * lookup; a Router, OnRamp, OffRamp, or TokenPool also work but add hops and need a + * configured lane. + */ + address: string + /** + * Address the registration is authorized against. Optional here, unlike `transferAdmin` and + * `acceptAdmin` which reject an omitted `sender`: leaving it out SKIPS the token-authority probe + * in {@link RegisterAdmin.buildUnsigned}, so the tx builds without that check and can then only + * fail on-chain. {@link RegisterAdmin.execute} defaults it to the signing wallet. + */ + sender?: string +} + +/** + * Registration through a v1.5.0 `RegistryModuleOwnerCustom` — that version has no + * `registerAccessControlDefaultAdmin`, so `registrationMethod` narrows to the two getter-derived + * paths and the AccessControl one will not typecheck. + */ +export type RegisterAdminParamsV1_5_0 = RegisterAdminBaseParams & { + registryModuleVersion: typeof RegistryModuleOwnerCustomVersion.V1_5_0 + /** Selects which token getter proves control; defaults to `owner`. */ + registrationMethod?: RegisterAdminMethodV1_5_0 +} + +/** + * Registration through a v1.6.0 `RegistryModuleOwnerCustom` — the default, and the only version + * offering `access-control-default-admin`. + */ +export type RegisterAdminParamsV1_6_0 = RegisterAdminBaseParams & { + registryModuleVersion?: typeof RegistryModuleOwnerCustomVersion.V1_6_0 + /** Selects how control is proved; defaults to `owner`. */ + registrationMethod?: RegisterAdminMethod +} + +/** + * Parameters for {@link RegisterAdmin}, discriminated on `registryModuleVersion`: `1.5.0` drops + * `access-control-default-admin` (a compile-time guarantee); omit it for the `1.6.0` default. + * {@link RegisterAdmin.buildUnsigned} verifies the declaration against the module's on-chain + * version. The administrator itself is never a parameter — see {@link REGISTRATION}. + */ +export type RegisterAdminParams = RegisterAdminParamsV1_5_0 | RegisterAdminParamsV1_6_0 + +/** + * Proposes a token's administrator in the TokenAdminRegistry via a RegistryModuleOwnerCustom. + * For `owner`/`ccip-admin` the module — not this op — derives the administrator from the token + * itself; for `access-control-default-admin` it registers the caller once a role check passes. + */ +export class RegisterAdmin extends EVMOperation { + readonly name = 'registerAdmin' + + /** Validates addresses and, if given, `registrationMethod`; no RPC. */ + protected override validate(p: RegisterAdminParams): void { + validateAddress(this.name, 'tokenAddress', p.tokenAddress) + validateAddress(this.name, 'registryModule', p.registryModule) + validateAddress(this.name, 'address', p.address) + if ( + p.registrationMethod !== undefined && + !Object.values(REGISTRATION_METHODS).includes(p.registrationMethod) + ) { + throw new CCTParamsInvalidError( + this.name, + 'registrationMethod', + `must be one of ${Object.values(REGISTRATION_METHODS).join(', ')}`, + ) + } + } + + /** + * Resolves the TAR, then runs the on-chain checks that would otherwise surface as an opaque + * revert, before encoding the module call. + */ + protected async buildUnsigned(chain: EVMChain, p: RegisterAdminParams): Promise { + const method = p.registrationMethod ?? REGISTRATION_METHODS.OWNER + const { moduleFn } = REGISTRATION[method] + + const registry = await chain.getTokenAdminRegistryFor(p.address) + + // The TAR reverts `OnlyRegistryModuleOrOwner` from deep inside the module call; check here. + if (!(await isRegistryModule(chain, registry, p.registryModule))) { + throw new CCTParamsInvalidError( + this.name, + 'registryModule', + `${p.registryModule} is not a registered module on the TokenAdminRegistry at ${registry}`, + ) + } + + // Both versions encode the shared functions identically, so a wrong `registryModuleVersion` + // would go unnoticed until the module rejected the call. Resolve and compare instead. + const onChainVersion = await resolveRegistryModuleOwnerCustom(chain, p.registryModule) + const declaredVersion = p.registryModuleVersion ?? RegistryModuleOwnerCustomVersion.V1_6_0 + if (onChainVersion !== declaredVersion) { + throw new CCTParamsInvalidError( + this.name, + 'registryModuleVersion', + `${p.registryModule} is a v${onChainVersion} RegistryModuleOwnerCustom, but v${declaredVersion} was declared`, + ) + } + + // Pre-flight the module's own authorization check (see REGISTRATION), so a mismatch fails + // here rather than as a `CanOnlySelfRegister`/`RequiredRoleNotFound` revert. Needs `sender`. + if (p.sender !== undefined) { + if (method === REGISTRATION_METHODS.ACCESS_CONTROL_DEFAULT_ADMIN) { + // Not `defaultAdmin()`: that lives on `AccessControlDefaultAdminRules`, not the plain + // `AccessControl` the module casts to. Mirror the module: read the role, then `hasRole`. + const accessControlInterface = new Interface([ + 'function DEFAULT_ADMIN_ROLE() view returns (bytes32)', + 'function hasRole(bytes32, address) view returns (bool)', + ]) + const token = new Contract(p.tokenAddress, accessControlInterface, chain.provider) + const role = (await token.getFunction('DEFAULT_ADMIN_ROLE')()) as string + const hasRole = (await token.getFunction('hasRole')(role, p.sender)) as boolean + if (!hasRole) { + throw new CCTParamsInvalidError( + this.name, + 'sender', + `must hold the token's DEFAULT_ADMIN_ROLE (AccessControl.hasRole) for registrationMethod "access-control-default-admin"`, + ) + } + } else { + const tokenGetter = REGISTRATION[method].tokenGetter! + const tokenGetterInterface = new Interface([ + `function ${tokenGetter}() view returns (address)`, + ]) + const admin = (await new Contract( + p.tokenAddress, + tokenGetterInterface, + chain.provider, + ).getFunction(tokenGetter)()) as string + if (getAddress(admin) !== getAddress(p.sender)) { + throw new CCTParamsInvalidError( + this.name, + 'sender', + `must equal token.${tokenGetter}() (${admin}) for registrationMethod "${method}"`, + ) + } + } + } + + // `proposeAdministrator` reverts `AlreadyRegistered` only once `administrator` is non-zero; + // a pending proposal is silently overwritten. Rejecting that too is deliberately stricter. + const { administrator, pendingAdministrator } = await readTokenAdminRegistryConfig( + chain, + registry, + p.tokenAddress, + ) + if (administrator !== ZeroAddress) { + throw new CCTParamsInvalidError( + this.name, + 'tokenAddress', + `token already has registry administrator ${administrator} — use transferAdmin to hand the role over, or setPool if you are already the admin`, + ) + } + if (pendingAdministrator !== ZeroAddress) { + throw new CCTParamsInvalidError( + this.name, + 'tokenAddress', + `a registration proposing ${pendingAdministrator} is already pending — that address must call acceptAdmin (re-registering would silently replace the proposal)`, + ) + } + + const data = getRegistryModuleOwnerCustomInterface(onChainVersion).encodeFunctionData( + moduleFn, + [p.tokenAddress], + ) + return callTx(p.registryModule, data) + } + + /** + * Signs and submits as the token's authority, defaulting `sender` to the signing wallet — the + * only address the module's `msg.sender` check can pass. See + * {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is rejected. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token-admin-registry/operations/set-pool.test.ts b/ccip-sdk/src/cct/evm/token-admin-registry/operations/set-pool.test.ts new file mode 100644 index 000000000..49edfa30b --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-admin-registry/operations/set-pool.test.ts @@ -0,0 +1,156 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { type SetPoolParams, SetPool } from './set-pool.ts' + +const TOKEN = '0x' + '11'.repeat(20) +const POOL = '0x' + '22'.repeat(20) +const ADDRESS = '0x' + '33'.repeat(20) +const TAR = '0x' + '44'.repeat(20) +const SENDER = '0x' + '55'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +const DATA = new Interface([ + 'function setPool(address localToken, address pool)', +]).encodeFunctionData('setPool', [TOKEN, POOL]) + +function stubChain(onAddress?: (address: string) => void): EVMChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + getTokenAdminRegistryFor: (address: string) => { + onAddress?.(address) + return Promise.resolve(TAR) + }, + nextNonce: async () => 0, + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +function fakeSigner(waitError?: Error) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(SENDER), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => (waitError ? Promise.reject(waitError) : Promise.resolve({ status: 1 })), + }), + } +} + +const op = new SetPool() + +function generate(chain: EVMChain, overrides: Partial = {}) { + return op.generate(chain, { + tokenAddress: TOKEN, + poolAddress: POOL, + address: ADDRESS, + sender: SENDER, + ...overrides, + }) +} + +describe('SetPool (cct/evm)', () => { + describe('generate', () => { + it('encodes setPool(token, pool) to the discovered TAR', async () => { + const unsigned = await generate(stubChain()) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, TAR) + assert.equal(tx.from, SENDER) + assert.equal(tx.data, DATA) + }) + + it('discovers the TAR from address', async () => { + let seen: string | undefined + await generate(stubChain((address) => (seen = address))) + assert.equal(seen, ADDRESS) + }) + + it('omits from when sender is not supplied', async () => { + const unsigned = await generate(stubChain(), { sender: undefined }) + assert.equal(unsigned.transactions[0]!.from, undefined) + }) + + it('allows the zero pool address to delist a token', async () => { + const unsigned = await generate(stubChain(), { poolAddress: ZeroAddress }) + assert.equal( + unsigned.transactions[0]!.data, + new Interface(['function setPool(address localToken, address pool)']).encodeFunctionData( + 'setPool', + [TOKEN, ZeroAddress], + ), + ) + }) + }) + + describe('validation', () => { + for (const param of ['tokenAddress', 'poolAddress', 'address', 'sender'] as const) { + it(`rejects an invalid ${param} before TAR discovery`, async () => { + let called = false + await assert.rejects( + () => + generate( + stubChain(() => (called = true)), + { [param]: 'not-an-address' }, + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setPool' && + err.context.param === param, + ) + assert.equal(called, false) + }) + } + }) + + describe('execute', () => { + it('signs and submits, resolving to the tx hash', async () => { + assert.deepEqual( + await op.execute(stubChain(), { + tokenAddress: TOKEN, + poolAddress: POOL, + address: ADDRESS, + wallet: fakeSigner(), + }), + { hash: HASH }, + ) + }) + + it('maps an on-chain revert to CCIPExecTxRevertedError', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + poolAddress: POOL, + address: ADDRESS, + wallet: fakeSigner(makeError('execution reverted', 'CALL_EXCEPTION')), + }), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && err.context.operation === 'setPool', + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + poolAddress: POOL, + address: ADDRESS, + wallet: {}, + }), + CCIPWalletInvalidError, + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-admin-registry/operations/set-pool.ts b/ccip-sdk/src/cct/evm/token-admin-registry/operations/set-pool.ts new file mode 100644 index 000000000..9955cbbcf --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-admin-registry/operations/set-pool.ts @@ -0,0 +1,49 @@ +/** + * setPool — registers a pool for a token in the TokenAdminRegistry. + * Version-independent (v1.5–v2.0 share one encoding). + * + * @packageDocumentation + */ + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { EVMOperation, callTx } from '../../operation.ts' +import { validateAddress } from '../../validate.ts' +import { getTokenAdminRegistryInterface } from '../contracts.ts' + +/** Parameters for `setPool`. Zero `poolAddress` delists the token. */ +export type SetPoolParams = { + tokenAddress: string + /** The zero address as `poolAddress` delists the token from the registry. */ + poolAddress: string + /** + * Contract to resolve the TokenAdminRegistry from. Pass the registry itself for a + * direct lookup; a Router, OnRamp, OffRamp, or TokenPool also work but add hops and + * need a configured lane. + */ + address: string + sender?: string +} + +/** Registers a pool for a token in the TokenAdminRegistry resolved from `address`. */ +export class SetPool extends EVMOperation { + readonly name = 'setPool' + + /** Validates all addresses before any RPC. */ + protected override validate(p: SetPoolParams): void { + validateAddress(this.name, 'tokenAddress', p.tokenAddress) + validateAddress(this.name, 'poolAddress', p.poolAddress) + validateAddress(this.name, 'address', p.address) + } + + /** Builds `setPool` calldata against the TokenAdminRegistry resolved from `address`. */ + protected async buildUnsigned(chain: EVMChain, p: SetPoolParams): Promise { + const to = await chain.getTokenAdminRegistryFor(p.address) + // TAR.setPool encoding is version-stable across v1.5–v2.0; no version dispatch needed. + const data = getTokenAdminRegistryInterface().encodeFunctionData('setPool', [ + p.tokenAddress, + p.poolAddress, + ]) + return callTx(to, data) + } +} diff --git a/ccip-sdk/src/cct/evm/token-admin-registry/operations/transfer-admin.test.ts b/ccip-sdk/src/cct/evm/token-admin-registry/operations/transfer-admin.test.ts new file mode 100644 index 000000000..25ae35d82 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-admin-registry/operations/transfer-admin.test.ts @@ -0,0 +1,301 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { ZeroAddress, getAddress } from 'ethers' + +import { CCIPWalletInvalidError } from '../../../../errors/index.ts' +import { interfaces } from '../../../../evm/const.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { type TransferAdminParams, TransferAdmin } from './transfer-admin.ts' + +const TOKEN = '0x' + '11'.repeat(20) +const ADDRESS = '0x' + '22'.repeat(20) +const TAR = '0x' + '33'.repeat(20) +const CURRENT_ADMIN = '0x' + '44'.repeat(20) +const NEW_ADMIN = '0x' + '55'.repeat(20) +const OTHER = '0x' + '66'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +const TRANSFER_ADMIN_ROLE_SELECTOR = + interfaces.TokenAdminRegistry.getFunction('transferAdminRole')!.selector +const EXPECTED_DATA = interfaces.TokenAdminRegistry.encodeFunctionData('transferAdminRole', [ + TOKEN, + NEW_ADMIN, +]) + +/** Encodes a `getTokenConfig` result the way the on-chain TAR would. */ +function encodeTokenConfig( + administrator: string, + pendingAdministrator = ZeroAddress, + tokenPool = ZeroAddress, +) { + return interfaces.TokenAdminRegistry.encodeFunctionResult('getTokenConfig', [ + [administrator, pendingAdministrator, tokenPool], + ]) +} + +/** + * Minimal EVMChain stub — a fake provider answers `getTokenConfig` reads via `call`. + * @remarks The provider asserts *what* it was asked rather than answering blindly, so every test in + * this file pins the read: a mutation that points the authorization pre-check at the wrong contract + * (e.g. the registry module instead of the resolved TAR) or at the wrong token would otherwise keep + * the whole suite green. `decodeFunctionData` also rejects a wrong-function mutation outright. + */ +function stubChain( + administrator = CURRENT_ADMIN, + opts: { + pendingAdministrator?: string + onAddress?: (address: string) => void + /** Token the pre-check is expected to read; defaults to the token under test. */ + expectToken?: string + } = {}, +): EVMChain { + return { + provider: { + call: async (tx: { to?: string; data?: string }) => { + assert.equal( + getAddress(tx.to ?? ZeroAddress), + getAddress(TAR), + 'pre-check must read the TAR resolved from `address`', + ) + const [readToken] = interfaces.TokenAdminRegistry.decodeFunctionData( + 'getTokenConfig', + tx.data ?? '0x', + ) as unknown as [string] + assert.equal( + getAddress(readToken), + getAddress(opts.expectToken ?? TOKEN), + 'pre-check must read the config of the token being transferred', + ) + return encodeTokenConfig(administrator, opts.pendingAdministrator) + }, + }, + logger: { debug() {}, info() {}, warn() {}, error() {} }, + getTokenAdminRegistryFor: (address: string) => { + opts.onAddress?.(address) + return Promise.resolve(TAR) + }, + nextNonce: async () => 0, + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +/** Fake ethers Signer whose broadcast resolves to a confirmed receipt. */ +function fakeSigner(address = CURRENT_ADMIN) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ hash: HASH, wait: () => Promise.resolve({ status: 1 }) }), + } +} + +const op = new TransferAdmin() + +function generate(chain: EVMChain, overrides: Partial = {}) { + return op.generate(chain, { + tokenAddress: TOKEN, + newAdmin: NEW_ADMIN, + address: ADDRESS, + sender: CURRENT_ADMIN, + ...overrides, + }) +} + +describe('TransferAdmin (cct/evm)', () => { + describe('generate', () => { + it('encodes transferAdminRole(token, newAdmin) to the discovered TAR', async () => { + const unsigned = await generate(stubChain()) + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + + const tx = unsigned.transactions[0]! + assert.equal(tx.to, TAR) + assert.equal(tx.from, CURRENT_ADMIN) + assert.ok( + tx.data!.startsWith(TRANSFER_ADMIN_ROLE_SELECTOR), + 'data starts with transferAdminRole selector', + ) + assert.equal(tx.data, EXPECTED_DATA) + }) + + it('discovers the TAR from the address param', async () => { + let seen: string | undefined + await generate(stubChain(CURRENT_ADMIN, { onAddress: (address) => (seen = address) })) + assert.equal(seen, ADDRESS) + }) + }) + + describe('validation', () => { + it('rejects an invalid tokenAddress before any RPC, tagged with the operation', async () => { + let called = false + const chain = stubChain(CURRENT_ADMIN, { onAddress: () => (called = true) }) + await assert.rejects( + () => generate(chain, { tokenAddress: 'not-an-address' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'transferAdmin' && + err.context.param === 'tokenAddress', + ) + assert.equal(called, false, 'validation fails before TAR discovery') + }) + + it('rejects an invalid newAdmin', async () => { + await assert.rejects( + () => generate(stubChain(), { newAdmin: 'not-an-address' }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'newAdmin', + ) + }) + + it('rejects an invalid address', async () => { + await assert.rejects( + () => generate(stubChain(), { address: 'not-an-address' }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'address', + ) + }) + + it('rejects a missing sender', async () => { + await assert.rejects( + () => generate(stubChain(), { sender: undefined }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects a sender that is not the current administrator', async () => { + await assert.rejects( + () => generate(stubChain(CURRENT_ADMIN), { sender: OTHER }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'sender' && + typeof err.context.reason === 'string' && + err.context.reason.includes('must be the current token administrator'), + ) + }) + + it('rejects a token that is not registered', async () => { + await assert.rejects( + () => generate(stubChain(ZeroAddress), { sender: OTHER }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'sender' && + typeof err.context.reason === 'string' && + err.context.reason.includes('is not registered'), + ) + }) + + it('distinguishes a registration still pending acceptance from not-registered', async () => { + await assert.rejects( + () => + generate(stubChain(ZeroAddress, { pendingAdministrator: NEW_ADMIN }), { + sender: OTHER, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'sender' && + typeof err.context.reason === 'string' && + err.context.reason.includes('still pending acceptance') && + err.context.reason.includes(NEW_ADMIN), + ) + }) + + it('rejects a zero-address sender on an unregistered token', async () => { + // Regression: the guard compared `administrator !== sender` before judging registration + // state, so a zero `sender` — which validateAddress permits — compared equal to an + // unregistered token's zero `administrator` and slipped past all three checks, emitting a + // transferAdminRole tx for a token with no admin to transfer. Registration state must be + // decided first, independently of who `sender` is. + await assert.rejects( + () => generate(stubChain(ZeroAddress), { sender: ZeroAddress }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'sender' && + typeof err.context.reason === 'string' && + err.context.reason.includes('is not registered'), + ) + }) + + it('rejects a zero-address sender on a token still pending acceptance', async () => { + // Same bypass, but the pending branch: still must not build, and must say why. + await assert.rejects( + () => + generate(stubChain(ZeroAddress, { pendingAdministrator: NEW_ADMIN }), { + sender: ZeroAddress, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'sender' && + typeof err.context.reason === 'string' && + err.context.reason.includes('still pending acceptance'), + ) + }) + }) + + describe('execute', () => { + it('signs and submits, resolving to the confirmed tx hash', async () => { + const result = await op.execute(stubChain(), { + tokenAddress: TOKEN, + newAdmin: NEW_ADMIN, + address: ADDRESS, + sender: CURRENT_ADMIN, + wallet: fakeSigner(CURRENT_ADMIN), + }) + assert.deepEqual(result, { hash: HASH }) + }) + + it('defaults sender to the wallet address when omitted', async () => { + // Uniform with registerAdmin/acceptAdmin: `sender` is required for generate() (buildUnsigned + // must know who to authorize against before encoding), but execute() can always derive it + // from the wallet — the only address that can satisfy the current-administrator check for a + // signed submission. Omitting it must therefore succeed, not fail validation. + const result = await op.execute(stubChain(), { + tokenAddress: TOKEN, + newAdmin: NEW_ADMIN, + address: ADDRESS, + wallet: fakeSigner(CURRENT_ADMIN), + }) + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + newAdmin: NEW_ADMIN, + address: ADDRESS, + sender: CURRENT_ADMIN, + wallet: {}, + }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + + it('rejects sender not matching the executing wallet, without reading the registry', async () => { + let readRegistry = false + const chain = stubChain(CURRENT_ADMIN, { onAddress: () => (readRegistry = true) }) + await assert.rejects( + () => + op.execute(chain, { + tokenAddress: TOKEN, + newAdmin: NEW_ADMIN, + address: ADDRESS, + // sender is a valid administrator, but not the address that `wallet` signs with — + // the registry read alone can't catch this (submit() clears tx.from before + // populate), so execute must compare sender against the wallet directly. + sender: CURRENT_ADMIN, + wallet: fakeSigner(OTHER), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'sender' && + typeof err.context.reason === 'string' && + err.context.reason.includes('must be the executing wallet'), + ) + assert.equal(readRegistry, false, 'rejected before reading the registry / broadcasting') + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-admin-registry/operations/transfer-admin.ts b/ccip-sdk/src/cct/evm/token-admin-registry/operations/transfer-admin.ts new file mode 100644 index 000000000..4a11608af --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-admin-registry/operations/transfer-admin.ts @@ -0,0 +1,146 @@ +/** + * transferAdmin — proposes a new TokenAdminRegistry administrator for a token + * (two-step; the proposed admin must separately call `acceptAdmin`). + * Version-independent (v1.5–v2.0 share one encoding). + * + * @remarks This is the registry's ADMIN role — the account allowed to call `setPool` + * ({@link SetPool}) and manage the token's CCT configuration in the `TokenAdminRegistry`. + * It is entirely distinct from a `TokenPool`'s Ownable2Step *owner* ({@link TransferOwnership}), + * which controls the pool contract itself (rate limits, remote-chain config, etc.). A token's + * registry admin and its pool's owner are commonly the same EOA/multisig, but the two roles + * live on different contracts and are transferred independently — do not confuse `transferAdmin` + * (this op) with `transferOwnership`. + * + * @packageDocumentation + */ + +import { ZeroAddress, getAddress } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { validateAddress } from '../../validate.ts' +import { getTokenAdminRegistryInterface, readTokenAdminRegistryConfig } from '../contracts.ts' + +/** + * Parameters for {@link TransferAdmin}. + * @remarks `sender` is typed optional to satisfy `EVMOperation`'s shared shape, but is required + * for {@link TransferAdmin.generate}: the pre-tx check below has nothing to compare + * `administrator` against without it, so an omitted `sender` is rejected in + * {@link TransferAdmin.parse}. {@link TransferAdmin.execute} relaxes this — it defaults + * `sender` to the signing wallet's own address, the only address that can satisfy the + * current-administrator check for a signed submission (see {@link TransferAdmin.execute}). + */ +export type TransferAdminParams = { + /** Token whose registry admin role is being handed over. */ + tokenAddress: string + /** The administrator proposed to accept the token's registry admin role. Pass {@link ZeroAddress} + * to cancel any pending transfer — the pending proposal is discarded without accepting the role. + */ + newAdmin: string + /** + * Contract to resolve the TokenAdminRegistry from. Pass the registry itself for a + * direct lookup; a Router, OnRamp, OffRamp, or TokenPool also work but add hops and + * need a configured lane. + */ + address: string + /** + * Current registry administrator. Required for {@link TransferAdmin.generate} + * (unsigned/offline flows) — `buildUnsigned` must read the registry and confirm the caller is + * the current administrator *before* encoding a tx, so it needs to know who that caller is up + * front. Optional for {@link TransferAdmin.execute}, which defaults it to the wallet's address + * — see the remarks above. + */ + sender?: string +} + +/** {@link TransferAdminParams} as {@link TransferAdmin.parse} leaves it: `sender` present and checksummed. */ +type ParsedTransferAdminParams = TransferAdminParams & { sender: string } + +/** + * Proposes a new TokenAdminRegistry administrator for a token via `transferAdminRole`. + * Two-step by design: `newAdmin` must separately call `acceptAdmin` to complete the handoff — + * this op alone does not change who can act as administrator. + */ +export class TransferAdmin extends EVMOperation { + readonly name = 'transferAdmin' + + /** + * Validates all addresses before any RPC, including the presence of `sender` (see above), and + * checksums `sender` so {@link buildUnsigned} can compare it against the registry's own + * checksummed `administrator` without re-asserting it. + */ + protected override parse(p: TransferAdminParams): ParsedTransferAdminParams { + validateAddress(this.name, 'tokenAddress', p.tokenAddress) + validateAddress(this.name, 'newAdmin', p.newAdmin) + validateAddress(this.name, 'address', p.address) + validateAddress(this.name, 'sender', p.sender) + return { ...p, sender: getAddress(p.sender) } + } + + /** + * Reads the registry directly, confirms `sender` is the current administrator, then builds + * `transferAdminRole` calldata against the TAR resolved from `address`. + */ + protected async buildUnsigned( + chain: EVMChain, + p: ParsedTransferAdminParams, + ): Promise { + const to = await chain.getTokenAdminRegistryFor(p.address) + const { administrator, pendingAdministrator } = await readTokenAdminRegistryConfig( + chain, + to, + p.tokenAddress, + ) + + const pending = pendingAdministrator === ZeroAddress ? undefined : pendingAdministrator + + // Registration state is checked BEFORE comparing against `sender`, and deliberately so: an + // unregistered token has a zero `administrator`, so an equality-first check would let + // `sender: ZeroAddress` (which validateAddress permits) compare equal to it and build a + // `transferAdminRole` tx for a token that has no admin to transfer. + if (administrator === ZeroAddress) { + throw new CCTParamsInvalidError( + this.name, + 'sender', + pending + ? `registration for this token is still pending acceptance by ${pending}; the pending administrator must accept the admin role first — this operation only transfers an accepted role` + : `token ${p.tokenAddress} is not registered in the TokenAdminRegistry at ${to}; call registerAdmin first`, + ) + } + if (administrator !== p.sender) { + throw new CCTParamsInvalidError( + this.name, + 'sender', + `must be the current token administrator (${administrator})`, + ) + } + + // TAR.transferAdminRole encoding is version-stable across v1.5–v2.0; no version dispatch needed. + const data = getTokenAdminRegistryInterface().encodeFunctionData('transferAdminRole', [ + p.tokenAddress, + p.newAdmin, + ]) + chain.logger.debug(`${this.name}: registry = ${to}, token = ${p.tokenAddress}`) + return callTx(to, data) + } + + /** + * Signs and submits as the current administrator, defaulting `sender` to the signing wallet — + * the only address that can satisfy {@link buildUnsigned}'s current-administrator check for a + * broadcast tx. See {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is + * rejected rather than signed. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address, or + * if any other param is invalid (see {@link buildUnsigned}) + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token-pool/contracts.test.ts b/ccip-sdk/src/cct/evm/token-pool/contracts.test.ts new file mode 100644 index 000000000..a0f20f227 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/contracts.test.ts @@ -0,0 +1,343 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface } from 'ethers' + +import { + CCTContractTypeInvalidError, + CCTContractVersionUnsupportedError, + CCTOperationUnsupportedError, +} from '../../errors.ts' +import { + TOKEN_POOL_FAMILIES, + TOKEN_POOL_INTERFACES, + TOKEN_POOL_TYPES, + TokenPoolVersion, + getTokenPoolFamily, + getTokenPoolInterface, + isLockReleaseTokenPoolType, + isTokenPoolType, + isTokenPoolVersion, + parseTokenPoolVersion, + resolveEncoder, +} from './contracts.ts' + +const ADDR = '0x' + '11'.repeat(20) + +describe('pool types', () => { + it('lists known EVM pool types (burn family + lock release)', () => { + assert.deepEqual( + [...TOKEN_POOL_TYPES].sort(), + [ + 'BurnFromMintTokenPool', + 'BurnMintTokenPool', + 'BurnMintWithLockReleaseFlagTokenPool', + 'BurnToAddressTokenPool', + 'BurnWithFromMintTokenPool', + 'LockReleaseTokenPool', + 'SiloedLockReleaseTokenPool', + ].sort(), + ) + }) + + it('isTokenPoolType accepts burn-family + lock-release, rejects others', () => { + assert.equal(isTokenPoolType('BurnMintTokenPool'), true) + assert.equal(isTokenPoolType('BurnFromMintTokenPool'), true) + assert.equal(isTokenPoolType('BurnWithFromMintTokenPool'), true) + assert.equal(isTokenPoolType('LockReleaseTokenPool'), true) + assert.equal(isTokenPoolType('UpgradeableLockReleaseTokenPool'), false) + assert.equal(isTokenPoolType('CCTPThroughCCVTokenPool'), false) + assert.equal(isTokenPoolType('TokenAdminRegistry'), false) + }) + + it('narrows lock-release types with isLockReleaseTokenPoolType, matching the family split', () => { + assert.equal(isLockReleaseTokenPoolType('LockReleaseTokenPool'), true) + assert.equal(isLockReleaseTokenPoolType('SiloedLockReleaseTokenPool'), true) + assert.equal(isLockReleaseTokenPoolType('BurnMintTokenPool'), false) + // the anchored ^Burn rule: a burn pool naming lock-release is still BurnMint + assert.equal(isLockReleaseTokenPoolType('BurnMintWithLockReleaseFlagTokenPool'), false) + // the predicate must agree with getTokenPoolFamily for every supported type + for (const type of TOKEN_POOL_TYPES) + assert.equal(isLockReleaseTokenPoolType(type), getTokenPoolFamily(type) === 'LockRelease') + }) + + it('maps burn-* variants to the BurnMint family, LockRelease to its own', () => { + assert.equal(getTokenPoolFamily('BurnFromMintTokenPool'), 'BurnMint') + assert.equal(getTokenPoolFamily('BurnWithFromMintTokenPool'), 'BurnMint') + assert.equal(getTokenPoolFamily('BurnToAddressTokenPool'), 'BurnMint') + assert.equal(getTokenPoolFamily('BurnMintWithLockReleaseFlagTokenPool'), 'BurnMint') + assert.equal(getTokenPoolFamily('LockReleaseTokenPool'), 'LockRelease') + }) +}) + +describe('pool versions', () => { + it('lists known EVM pool versions low→high', () => { + assert.deepEqual(Object.values(TokenPoolVersion), [ + TokenPoolVersion.V1_5_0, + TokenPoolVersion.V1_5_1, + TokenPoolVersion.V1_6_1, + TokenPoolVersion.V2_0_0, + ]) + }) + + it('isTokenPoolVersion narrows known versions and rejects others', () => { + assert.equal(isTokenPoolVersion(TokenPoolVersion.V1_5_1), true) + assert.equal(isTokenPoolVersion(TokenPoolVersion.V2_0_0), true) + // `1.6.0` is a real on-chain string, but no ABI is vendored for it — deferred, not unknown + assert.equal(isTokenPoolVersion('1.6.0'), false) + assert.equal(isTokenPoolVersion('garbage'), false) + }) +}) + +describe('parseTokenPoolVersion', () => { + it('returns { type, version } for a known pool type+version', () => { + assert.deepEqual( + parseTokenPoolVersion({ address: ADDR, contractType: 'BurnMintTokenPool', version: '1.5.1' }), + { + type: 'BurnMintTokenPool', + version: TokenPoolVersion.V1_5_1, + }, + ) + assert.deepEqual( + parseTokenPoolVersion({ + address: ADDR, + contractType: 'LockReleaseTokenPool', + version: '2.0.0', + }), + { + type: 'LockReleaseTokenPool', + version: TokenPoolVersion.V2_0_0, + }, + ) + }) + + it('throws CCTContractTypeInvalidError for an unsupported pool type', () => { + assert.throws( + () => + parseTokenPoolVersion({ + address: ADDR, + contractType: 'TokenAdminRegistry', + version: '1.5.1', + }), + CCTContractTypeInvalidError, + ) + }) + + it('throws CCTContractTypeInvalidError for UpgradeableLockReleaseTokenPool (not in TOKEN_POOL_TYPES)', () => { + assert.throws( + () => + parseTokenPoolVersion({ + address: ADDR, + contractType: 'UpgradeableLockReleaseTokenPool', + version: '1.5.1', + }), + CCTContractTypeInvalidError, + ) + }) + + it('narrows a burn-family variant to its exact type', () => { + assert.deepEqual( + parseTokenPoolVersion({ + address: ADDR, + contractType: 'BurnFromMintTokenPool', + version: '1.5.1', + }), + { type: 'BurnFromMintTokenPool', version: TokenPoolVersion.V1_5_1 }, + ) + }) + + it('normalizes the v1.5.0 *AndProxy shims to their base type', () => { + for (const [contractType, type] of [ + ['BurnMintTokenPoolAndProxy', 'BurnMintTokenPool'], + ['BurnFromMintTokenPoolAndProxy', 'BurnFromMintTokenPool'], + ['BurnWithFromMintTokenPoolAndProxy', 'BurnWithFromMintTokenPool'], + ['LockReleaseTokenPoolAndProxy', 'LockReleaseTokenPool'], + ] as const) { + assert.deepEqual(parseTokenPoolVersion({ address: ADDR, contractType, version: '1.5.0' }), { + type, + version: TokenPoolVersion.V1_5_0, + }) + } + }) + + it('only strips AndProxy at v1.5.0 — the shim exists at no other version', () => { + for (const version of ['1.5.1', '1.6.1', '2.0.0']) { + assert.throws( + () => + parseTokenPoolVersion({ + address: ADDR, + contractType: 'BurnMintTokenPoolAndProxy', + version, + }), + CCTContractTypeInvalidError, + ) + } + }) + + it('gates the stripped base type, so an unsupported AndProxy name is still rejected', () => { + assert.throws( + () => + parseTokenPoolVersion({ + address: ADDR, + contractType: 'UpgradeableLockReleaseTokenPoolAndProxy', + version: '1.5.0', + }), + CCTContractTypeInvalidError, + ) + }) + + it('throws CCTContractVersionUnsupportedError for an unknown version', () => { + assert.throws( + () => + parseTokenPoolVersion({ + address: ADDR, + contractType: 'BurnMintTokenPool', + version: '1.7.0', + }), + CCTContractVersionUnsupportedError, + ) + }) +}) + +describe('TOKEN_POOL_INTERFACES', () => { + it('provides a cached ethers Interface for each family and version', () => { + for (const family of TOKEN_POOL_FAMILIES) { + for (const version of Object.values(TokenPoolVersion)) { + assert.ok(TOKEN_POOL_INTERFACES[family][version] instanceof Interface) + } + } + }) + + it('resolves distinct Interfaces per family at the same version', () => { + assert.notEqual( + TOKEN_POOL_INTERFACES.BurnMint[TokenPoolVersion.V1_5_1], + TOKEN_POOL_INTERFACES.LockRelease[TokenPoolVersion.V1_5_1], + ) + }) + + it('uses the *_and_proxy variant at V1_5_0 (exposes getPreviousPool)', () => { + assert.ok( + TOKEN_POOL_INTERFACES.BurnMint[TokenPoolVersion.V1_5_0].hasFunction('getPreviousPool'), + ) + assert.ok( + !TOKEN_POOL_INTERFACES.BurnMint[TokenPoolVersion.V1_5_1].hasFunction('getPreviousPool'), + ) + }) +}) + +describe('getTokenPoolInterface', () => { + it('returns the cached family Interface for the type+version (same instance across calls)', () => { + const a = getTokenPoolInterface('BurnMintTokenPool', TokenPoolVersion.V1_5_1) + const b = getTokenPoolInterface('BurnMintTokenPool', TokenPoolVersion.V1_5_1) + assert.ok(a instanceof Interface) + assert.equal(a, b) + assert.equal(a, TOKEN_POOL_INTERFACES.BurnMint[TokenPoolVersion.V1_5_1]) + }) + + it('resolves all burn-* variants to the same BurnMint-family Interface', () => { + const burnMint = getTokenPoolInterface('BurnMintTokenPool', TokenPoolVersion.V1_5_1) + assert.equal(getTokenPoolInterface('BurnFromMintTokenPool', TokenPoolVersion.V1_5_1), burnMint) + assert.equal( + getTokenPoolInterface('BurnWithFromMintTokenPool', TokenPoolVersion.V1_5_1), + burnMint, + ) + assert.equal(getTokenPoolInterface('BurnToAddressTokenPool', TokenPoolVersion.V1_5_1), burnMint) + }) + + it('resolves LockRelease to a different Interface than the BurnMint family', () => { + assert.notEqual( + getTokenPoolInterface('BurnMintTokenPool', TokenPoolVersion.V1_6_1), + getTokenPoolInterface('LockReleaseTokenPool', TokenPoolVersion.V1_6_1), + ) + }) +}) + +describe('resolveEncoder', () => { + it('floor-matches to the encoder at the greatest version ≤ requested', () => { + const encoders = { + [TokenPoolVersion.V1_5_0]: () => 'a', + [TokenPoolVersion.V2_0_0]: () => 'b', + } + assert.equal(resolveEncoder(encoders, TokenPoolVersion.V1_5_0, 'op')(), 'a') + assert.equal(resolveEncoder(encoders, TokenPoolVersion.V1_6_1, 'op')(), 'a') + assert.equal(resolveEncoder(encoders, TokenPoolVersion.V2_0_0, 'op')(), 'b') + }) + + it('throws when nothing is registered at or below the version', () => { + assert.throws( + () => resolveEncoder({ [TokenPoolVersion.V2_0_0]: () => 'b' }, TokenPoolVersion.V1_5_0, 'op'), + CCTOperationUnsupportedError, + ) + }) + + it('inherits the lower version\u2019s encoder across every absent key above it', () => { + // a single V1_5_0 entry \u2014 the shape `transfer-ownership.ts` uses \u2014 must cover every version + const encoders = { [TokenPoolVersion.V1_5_0]: () => 'only' } + for (const version of Object.values(TokenPoolVersion)) + assert.equal(resolveEncoder(encoders, version, 'op')(), 'only') + }) + + it('stops at an explicit null ceiling instead of inheriting the encoder downward', () => { + // `applyAllowListUpdates`: present 1.5.0\u20131.6.1, removed outright in 2.0.0 + const encoders = { + [TokenPoolVersion.V1_5_0]: () => 'allowList', + [TokenPoolVersion.V2_0_0]: null, + } + assert.equal(resolveEncoder(encoders, TokenPoolVersion.V1_5_0, 'op')(), 'allowList') + assert.equal(resolveEncoder(encoders, TokenPoolVersion.V1_5_1, 'op')(), 'allowList') + assert.equal(resolveEncoder(encoders, TokenPoolVersion.V1_6_1, 'op')(), 'allowList') + assert.throws( + () => resolveEncoder(encoders, TokenPoolVersion.V2_0_0, 'op'), + (error: unknown) => + error instanceof CCTOperationUnsupportedError && + error.context.operation === 'op' && + error.context.version === TokenPoolVersion.V2_0_0, + ) + }) + + it('applies a null ceiling to every version at or above it, not just the keyed one', () => { + // a ceiling keyed below the top must not be escaped by asking for a higher version + const encoders = { + [TokenPoolVersion.V1_5_0]: () => 'a', + [TokenPoolVersion.V1_6_1]: null, + } + assert.equal(resolveEncoder(encoders, TokenPoolVersion.V1_5_1, 'op')(), 'a') + assert.throws( + () => resolveEncoder(encoders, TokenPoolVersion.V1_6_1, 'op'), + CCTOperationUnsupportedError, + ) + assert.throws( + () => resolveEncoder(encoders, TokenPoolVersion.V2_0_0, 'op'), + CCTOperationUnsupportedError, + ) + }) + + it('lets a later version re-register an encoder above a null ceiling', () => { + // the walk is downward-from-requested, so a re-added function is found before the ceiling + const encoders = { + [TokenPoolVersion.V1_5_0]: () => 'old', + [TokenPoolVersion.V1_5_1]: null, + [TokenPoolVersion.V2_0_0]: () => 'new', + } + assert.equal(resolveEncoder(encoders, TokenPoolVersion.V1_5_0, 'op')(), 'old') + assert.throws( + () => resolveEncoder(encoders, TokenPoolVersion.V1_6_1, 'op'), + CCTOperationUnsupportedError, + ) + assert.equal(resolveEncoder(encoders, TokenPoolVersion.V2_0_0, 'op')(), 'new') + }) + + it('throws when the requested version itself is the only null entry', () => { + assert.throws( + () => resolveEncoder({ [TokenPoolVersion.V1_5_0]: null }, TokenPoolVersion.V1_5_0, 'op'), + CCTOperationUnsupportedError, + ) + }) + + it('throws on an empty table', () => { + assert.throws( + () => resolveEncoder({}, TokenPoolVersion.V1_6_1, 'op'), + CCTOperationUnsupportedError, + ) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-pool/contracts.ts b/ccip-sdk/src/cct/evm/token-pool/contracts.ts new file mode 100644 index 000000000..2f8ba21f4 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/contracts.ts @@ -0,0 +1,387 @@ +/** + * EVM token-pool contract layer for CCT: cached {@link Interface}s + on-chain type/version + * resolution ({@link resolveTokenPool}, {@link getTokenPoolInterface}, floor-matched via + * {@link resolveEncoder}) for read/write ops, plus the deployable pools' creation artifacts + * ({@link getTokenPoolArtifact}), the narrow role reads every owner-gated write pre-flights + * `sender` against ({@link readTokenPoolOwner}, {@link readTokenPoolRateLimitAdmin}), the allowlist read + * `applyAllowlistUpdates` pre-flights against ({@link readTokenPoolAllowlist}) plus the + * owner-only guard built on the first of them ({@link assertPoolOwner}). The write-side + * rate-limit shape lane-config ops share lives in `rate-limit.ts`. Mirrors `token/contracts.ts`. + * + * @packageDocumentation + */ + +import { Interface, getAddress } from 'ethers' +import type { TypedContract } from 'ethers-abitype' + +import type { EVMChain } from '../../../evm/index.ts' +import { resultToObject } from '../../../evm/types.ts' +import { + CCTContractTypeInvalidError, + CCTContractVersionUnsupportedError, + CCTOperationUnsupportedError, + CCTParamsInvalidError, +} from '../../errors.ts' +import BURN_MINT_TOKEN_POOL_V1_5_0_ABI from '../artifacts/abi/V1_5_0/burn-mint-token-pool-and-proxy.ts' +import LOCK_RELEASE_TOKEN_POOL_V1_5_0_ABI from '../artifacts/abi/V1_5_0/lock-release-token-pool-and-proxy.ts' +import BURN_MINT_TOKEN_POOL_V1_5_1_ABI from '../artifacts/abi/V1_5_1/burn-mint-token-pool.ts' +import LOCK_RELEASE_TOKEN_POOL_V1_5_1_ABI from '../artifacts/abi/V1_5_1/lock-release-token-pool.ts' +import BURN_MINT_TOKEN_POOL_V1_6_1_ABI from '../artifacts/abi/V1_6_1/burn-mint-token-pool.ts' +import LOCK_RELEASE_TOKEN_POOL_V1_6_1_ABI from '../artifacts/abi/V1_6_1/lock-release-token-pool.ts' +import BURN_MINT_TOKEN_POOL_V2_0_0_ABI from '../artifacts/abi/V2_0_0/burn-mint-token-pool.ts' +import LOCK_RELEASE_TOKEN_POOL_V2_0_0_ABI from '../artifacts/abi/V2_0_0/lock-release-token-pool.ts' +import BURN_FROM_MINT_TOKEN_POOL_V2_0_0_BYTECODE from '../artifacts/bytecode/V2_0_0/burn-from-mint-token-pool.ts' +import BURN_MINT_TOKEN_POOL_V2_0_0_BYTECODE from '../artifacts/bytecode/V2_0_0/burn-mint-token-pool.ts' +import BURN_WITH_FROM_MINT_TOKEN_POOL_V2_0_0_BYTECODE from '../artifacts/bytecode/V2_0_0/burn-with-from-mint-token-pool.ts' +import LOCK_RELEASE_TOKEN_POOL_V2_0_0_BYTECODE from '../artifacts/bytecode/V2_0_0/lock-release-token-pool.ts' +import type { DeployArtifact } from '../operation.ts' +import { getTypedContract } from '../query.ts' + +/** + * ABI families for pool resolution. The burn-* variants are interface-compatible for CCT + * ops (identical constructor + `transferOwnership`, shared TokenPool surface), so they share + * the `BurnMint` ABI; `LockRelease` (with its liquidity functions) is distinct. + */ +export const TOKEN_POOL_FAMILIES = ['BurnMint', 'LockRelease'] as const + +/** An ABI family for pool resolution. */ +export type TokenPoolFamily = (typeof TOKEN_POOL_FAMILIES)[number] + +/** + * Supported on-chain `typeAndVersion` pool types. The burn-* variants are interface-compatible + * for CCT ops and share the `BurnMint` ABI (see {@link getTokenPoolFamily}); `LockReleaseTokenPool` + * is distinct. Unsupported values fail in {@link parseTokenPoolVersion}, which also normalizes + * v1.5.0's `*AndProxy` shims onto these base names. + */ +export const TOKEN_POOL_TYPES = [ + 'BurnMintTokenPool', + 'BurnFromMintTokenPool', + 'BurnWithFromMintTokenPool', + 'BurnToAddressTokenPool', + 'BurnMintWithLockReleaseFlagTokenPool', + 'LockReleaseTokenPool', + 'SiloedLockReleaseTokenPool', +] as const + +/** A supported EVM token-pool contract type. */ +export type TokenPoolType = (typeof TOKEN_POOL_TYPES)[number] + +/** The burn-* mint pool types, which share the `BurnMint` ABI. */ +export type BurnMintTokenPoolType = Extract + +/** The lock/release pool types, which share the `LockRelease` ABI. */ +export type LockReleaseTokenPoolType = Exclude + +/** Type guard for {@link TOKEN_POOL_TYPES}. */ +export function isTokenPoolType(v: string): v is TokenPoolType { + return (TOKEN_POOL_TYPES as readonly string[]).includes(v) +} + +/** + * Classifies a supported pool type into its ABI {@link TokenPoolFamily} by name: every burn-* pool + * shares the `BurnMint` ABI (hence the anchored `^Burn`, which also covers + * `BurnMintWithLockReleaseFlagTokenPool`), and the rest share `LockRelease`. + * {@link TOKEN_POOL_TYPES} is the gate, so only allowlisted, ABI-compatible names reach here. + */ +export function getTokenPoolFamily(type: TokenPoolType): TokenPoolFamily { + return /^Burn/.test(type) ? 'BurnMint' : 'LockRelease' +} + +/** Narrows a pool type to the {@link LockReleaseTokenPoolType}s, per {@link getTokenPoolFamily}. */ +export function isLockReleaseTokenPoolType(type: TokenPoolType): type is LockReleaseTokenPoolType { + return getTokenPoolFamily(type) === 'LockRelease' +} + +/** Known pool versions, low to high. Value order drives floor-match in {@link resolveEncoder}. */ +export const TokenPoolVersion = { + V1_5_0: '1.5.0', + V1_5_1: '1.5.1', + V1_6_1: '1.6.1', + V2_0_0: '2.0.0', +} as const + +/** A known EVM token-pool version. */ +export type TokenPoolVersion = (typeof TokenPoolVersion)[keyof typeof TokenPoolVersion] + +/** Type guard for {@link TokenPoolVersion}. */ +export function isTokenPoolVersion(v: string): v is TokenPoolVersion { + return Object.values(TokenPoolVersion).some((known) => known === v) +} + +/** + * Narrows raw `typeAndVersion` strings to a known {@link TokenPoolType} and + * {@link TokenPoolVersion}. A v1.5.0 `*AndProxy` type normalizes to its base pool type. + * @throws {@link CCTContractTypeInvalidError} if `contractType` is not a supported pool type + * @throws {@link CCTContractVersionUnsupportedError} if `version` is not a known pool version + */ +export function parseTokenPoolVersion({ + address, + contractType, + version, +}: { + address: string + contractType: string + version: string +}): { type: TokenPoolType; version: TokenPoolVersion } { + // v1.5.0's `*AndProxy` shims override only lockOrBurn/releaseOrMint, so every function a CCT op + // encodes is the base pool's — and the vendored v1.5.0 ABIs are the `*_and_proxy` ones already. + const type = + version === TokenPoolVersion.V1_5_0 ? contractType.replace(/AndProxy$/, '') : contractType + if (!isTokenPoolType(type)) + throw new CCTContractTypeInvalidError(address, TOKEN_POOL_TYPES.join(', '), contractType) + if (!isTokenPoolVersion(version)) + throw new CCTContractVersionUnsupportedError(contractType, version, { + context: { address }, + }) + + return { type, version } +} + +/** + * Resolves an on-chain pool's type + version from its `typeAndVersion`, narrowed to a known + * {@link TokenPoolType} and {@link TokenPoolVersion}. + * @throws {@link CCTContractTypeInvalidError} if the reported type is not a supported pool type + * @throws {@link CCTContractVersionUnsupportedError} if the reported version is not a known pool version + */ +export async function resolveTokenPool( + chain: EVMChain, + address: string, +): Promise<{ type: TokenPoolType; version: TokenPoolVersion }> { + const [contractType, version] = await chain.typeAndVersion(address) + return parseTokenPoolVersion({ address, contractType, version }) +} + +/** `Ownable2Step.owner()`, identical across all supported pool types and versions. */ +type PoolOwnerGetter = Pick, 'owner'> + +/** + * Pre-flights `sender` against the pool's on-chain `owner()` for an owner-gated write, so an + * unauthorized caller fails as a {@link CCTParamsInvalidError} here instead of as an opaque + * `OwnableUnauthorizedAccount` revert after a multisig has already reviewed and signed. + * + * @remarks A single `owner()` call, not the full `getTokenPoolState` query: `owner` is the only + * field this needs and the only one whose getter never changed spelling, so reading it directly + * costs one `eth_call` instead of a second `typeAndVersion` resolution plus every admin field. + * @remarks For an owner-*only* gate. Not for a gate that accepts more than the owner — + * `setChainRateLimiterConfigs` takes `owner` **or** `rateLimitAdmin`, and collapsing that + * disjunction to this helper would lock out a delegated rate-limit admin. + * @param operation - Operation name, for the error's `operation` field. + * @param chain - Chain to read the owner from. + * @param poolAddress - Token pool being written to. + * @param sender - The address the tx will be sent from; compared checksummed. + * @throws {@link CCTParamsInvalidError} if `sender` is not the pool owner + */ +export async function assertPoolOwner( + operation: string, + chain: EVMChain, + poolAddress: string, + sender: string, +): Promise { + const owner = await readTokenPoolOwner(chain, poolAddress) + if (getAddress(sender) === owner) return + throw new CCTParamsInvalidError( + operation, + 'sender', + `must be the current token pool owner (${owner})`, + ) +} + +/** + * Cached pool {@link Interface}s per {@link TokenPoolFamily} and {@link TokenPoolVersion}, + * built once from the vendored `artifacts/` ABIs (no per-call `new Interface`). `V1_5_0` + * uses the `*_and_proxy` variants — the only form `@chainlink/contracts-ccip` ships at 1.5.0. + */ +export const TOKEN_POOL_INTERFACES: Record> = { + BurnMint: { + [TokenPoolVersion.V1_5_0]: new Interface(BURN_MINT_TOKEN_POOL_V1_5_0_ABI), + [TokenPoolVersion.V1_5_1]: new Interface(BURN_MINT_TOKEN_POOL_V1_5_1_ABI), + [TokenPoolVersion.V1_6_1]: new Interface(BURN_MINT_TOKEN_POOL_V1_6_1_ABI), + [TokenPoolVersion.V2_0_0]: new Interface(BURN_MINT_TOKEN_POOL_V2_0_0_ABI), + }, + LockRelease: { + [TokenPoolVersion.V1_5_0]: new Interface(LOCK_RELEASE_TOKEN_POOL_V1_5_0_ABI), + [TokenPoolVersion.V1_5_1]: new Interface(LOCK_RELEASE_TOKEN_POOL_V1_5_1_ABI), + [TokenPoolVersion.V1_6_1]: new Interface(LOCK_RELEASE_TOKEN_POOL_V1_6_1_ABI), + [TokenPoolVersion.V2_0_0]: new Interface(LOCK_RELEASE_TOKEN_POOL_V2_0_0_ABI), + }, +} + +/** + * Returns the cached pool {@link Interface} for `type` and `version`, selected by the + * type's {@link TokenPoolFamily}. Never throws when both came from + * {@link parseTokenPoolVersion}. + */ +export function getTokenPoolInterface(type: TokenPoolType, version: TokenPoolVersion): Interface { + return TOKEN_POOL_INTERFACES[getTokenPoolFamily(type)][version] +} + +/** + * Reads a token pool's Ownable2Step `owner()` in a single `eth_call`. The one owner read every + * owner-gated pool write op pre-flights `sender` against. + * + * @remarks No `version` parameter and no family dispatch: `owner()` is declared identically — + * same selector, same `address` return — by both {@link TOKEN_POOL_FAMILIES} at all four + * supported versions, so the v1.5.0 `BurnMint` interface types the call for every pool. + * @remarks **Deliberately not routed through the `getTokenPoolState` query op, and must not be + * "simplified" back to it.** Two reasons, the first of which is a correctness bug and not just a + * cost concern: + * + * 1. `getTokenPoolState` throws {@link CCTContractTypeInvalidError} for a v2.0.0 + * `SiloedLockReleaseTokenPool`, because that pool escrows per remote chain + * (`getLockBox(uint64)`) and so has no single `lockBox` field for the query's result shape to + * report. `SiloedLockReleaseTokenPool` is nonetheless a supported {@link TokenPoolType}, and + * the write ops' calldata is perfectly valid against it. Gating an owner check through that + * query would therefore make every one of those ops permanently unusable on siloed pools — + * failing on an unrelated result-shape limitation while `generateUnsigned*` works fine. + * 2. It costs 6–8 `eth_call`s (token, router, RMN proxy, rate-limit admin, supported chains, + * dynamic config, finality config, lockbox) plus a `getTokenInfo` round trip, and re-resolves + * `typeAndVersion`, all to obtain one address. + * + * This mirrors `token-admin-registry/operations/transfer-admin.ts`, which likewise does its own + * narrow pre-tx read rather than going through a read op. + * @param chain - Chain to read from. + * @param poolAddress - Token pool contract to read `owner()` from. + * @returns The current owner, checksummed. + */ +export async function readTokenPoolOwner(chain: EVMChain, poolAddress: string): Promise { + const pool: PoolOwnerGetter = getTypedContract( + chain, + poolAddress, + BURN_MINT_TOKEN_POOL_V1_5_0_ABI, + ) + return getAddress(resultToObject(await pool.owner())) +} + +/** + * `TokenPool`'s allowlist getters, identical across v1.5.0–v1.6.1 and both ABI families. Absent + * from v2.0.0, which dropped the allowlist — callers must resolve the version first. + */ +type PoolAllowlistGetter = Pick< + TypedContract, + 'getAllowListEnabled' | 'getAllowList' +> + +/** + * Reads a token pool's sender allowlist and whether the feature is enabled at all, in two + * parallel `eth_call`s. + * + * @remarks Same rationale as {@link readTokenPoolOwner} for not routing through + * `getTokenPoolState`, which does not expose the allowlist. + * @remarks `enabled` is fixed for the pool's lifetime: the contract sets `i_allowlistEnabled` + * *immutable* in its constructor, to `allowlist.length > 0`. A pool deployed without an + * allowlist can therefore never gain one, and every `applyAllowListUpdates` against it reverts + * `AllowListNotEnabled`. + * @param chain - Chain to read from. + * @param poolAddress - Token pool contract to read from; must be v1.5.0–v1.6.1. + * @returns `enabled`, and the current entries checksummed (empty when disabled). + */ +export async function readTokenPoolAllowlist( + chain: EVMChain, + poolAddress: string, +): Promise<{ enabled: boolean; entries: string[] }> { + const pool: PoolAllowlistGetter = getTypedContract( + chain, + poolAddress, + BURN_MINT_TOKEN_POOL_V1_5_0_ABI, + ) + const [enabled, entries] = await Promise.all([pool.getAllowListEnabled(), pool.getAllowList()]) + return { + enabled: resultToObject(enabled), + entries: resultToObject(entries).map((entry) => getAddress(entry)), + } +} + +/** + * Reads a token pool's `rateLimitAdmin` — the delegated role the pools accept for rate-limit + * writes alongside the owner — in a single `eth_call`. + * + * @remarks Same rationale as {@link readTokenPoolOwner} for not routing through + * `getTokenPoolState`. + * @remarks Version-dispatched, unlike `owner()`: v1.5.0–v1.6.1 expose a standalone + * `getRateLimitAdmin()`, while v2.0.0 folded the role into `getDynamicConfig()`'s + * `(router, rateLimitAdmin, feeAdmin)` triple. + * @param chain - Chain to read from. + * @param poolAddress - Token pool contract to read from. + * @param version - Pool version, as resolved by {@link resolveTokenPool}; selects the getter. + * @returns The current rate-limit admin, checksummed. The zero address when the role is unset — + * callers must treat that as "matches nobody" rather than comparing it directly. + */ +export async function readTokenPoolRateLimitAdmin( + chain: EVMChain, + poolAddress: string, + version: TokenPoolVersion, +): Promise { + if (version === TokenPoolVersion.V2_0_0) { + const pool = getTypedContract(chain, poolAddress, BURN_MINT_TOKEN_POOL_V2_0_0_ABI) + // getDynamicConfig returns (router, rateLimitAdmin, feeAdmin); index the raw Result rather + // than resultToObject it, which would turn the named tuple into an object (see + // get-token-pool-state.ts). + const dynamicConfig = await pool.getDynamicConfig() + return getAddress(dynamicConfig[1] as string) + } + const pool = getTypedContract(chain, poolAddress, BURN_MINT_TOKEN_POOL_V1_5_1_ABI) + return getAddress(resultToObject(await pool.getRateLimitAdmin())) +} + +/** + * Creation bytecode per deployable pool type (2.0.0 only — pre-2.0.0 bytecode is not vendored). + * The keys define the deployable set ({@link DeployableTokenPoolType}). The burn-* variants share + * the `BurnMint` constructor ABI but are distinct contracts with distinct bytecode. + */ +const TOKEN_POOL_BYTECODE = { + BurnMintTokenPool: BURN_MINT_TOKEN_POOL_V2_0_0_BYTECODE, + BurnFromMintTokenPool: BURN_FROM_MINT_TOKEN_POOL_V2_0_0_BYTECODE, + BurnWithFromMintTokenPool: BURN_WITH_FROM_MINT_TOKEN_POOL_V2_0_0_BYTECODE, + LockReleaseTokenPool: LOCK_RELEASE_TOKEN_POOL_V2_0_0_BYTECODE, +} satisfies Partial> + +/** A pool contract type that can be deployed (has vendored 2.0.0 creation bytecode). */ +export type DeployableTokenPoolType = keyof typeof TOKEN_POOL_BYTECODE + +/** Type guard for {@link DeployableTokenPoolType} (has vendored 2.0.0 creation bytecode). */ +export function isDeployableTokenPoolType(type: string): type is DeployableTokenPoolType { + return Object.hasOwn(TOKEN_POOL_BYTECODE, type) +} + +/** + * Deploy artifact for a deployable pool `type` (v2.0.0): contract name (= `type`), the cached + * constructor {@link Interface}, and the creation bytecode. + */ +export function getTokenPoolArtifact(type: DeployableTokenPoolType): DeployArtifact { + return { + contract: type, + iface: getTokenPoolInterface(type, TokenPoolVersion.V2_0_0), + bytecode: TOKEN_POOL_BYTECODE[type], + } +} + +/** + * Returns the encoder registered at the greatest version less than or equal to `version`, + * walking {@link TokenPoolVersion} downwards from `version`. + * + * A table entry says one of two things: + * - **absent key** — the calldata did not change here, so it *inherits* the closest lower entry. + * One entry per calldata change therefore covers every higher version. + * - **explicit `null`** — the function was removed at this version, so the op is reported + * unsupported rather than emitting calldata for a selector the pool does not implement. + * + * @param encoders - Sparse table keyed by {@link TokenPoolVersion}; `null` marks a removal ceiling. + * @param version - The resolved on-chain pool version to encode for. + * @param op - Operation name, for the error. + * @throws {@link CCTOperationUnsupportedError} if nothing is registered at or below `version`, or + * if the walk hits an explicit `null` ceiling first + */ +export function resolveEncoder( + encoders: Partial>, + version: TokenPoolVersion, + op: string, +): F { + const versions = Object.values(TokenPoolVersion) + for (let i = versions.indexOf(version); i >= 0; i--) { + const encoder = encoders[versions[i]!] + // removed here — do not inherit the lower encoder downward + if (encoder === null) break + if (encoder !== undefined) return encoder + } + throw new CCTOperationUnsupportedError(op, version) +} diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/add-remote-pool.test.ts b/ccip-sdk/src/cct/evm/token-pool/operations/add-remote-pool.test.ts new file mode 100644 index 000000000..294775c38 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/add-remote-pool.test.ts @@ -0,0 +1,428 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError, toBeHex, zeroPadValue } from 'ethers' + +import type { TokenPoolRemote } from '../../../../chain.ts' +import { + CCIPExecTxRevertedError, + CCIPTokenPoolChainConfigNotFoundError, + CCIPWalletInvalidError, +} from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { parseTypeAndVersion } from '../../../../utils.ts' +import { CCTOperationUnsupportedError, CCTParamsInvalidError } from '../../../errors.ts' +import { + type TokenPoolType, + TOKEN_POOL_INTERFACES, + TokenPoolVersion, + getTokenPoolFamily, +} from '../contracts.ts' +import { type AddRemotePoolParams, AddRemotePool } from './add-remote-pool.ts' + +const POOL = '0x' + '11'.repeat(20) +const TOKEN = '0x' + '22'.repeat(20) +const ROUTER = '0x' + '33'.repeat(20) +const OWNER = '0x' + '44'.repeat(20) +const RMN_PROXY = '0x' + '55'.repeat(20) +const RATE_LIMIT_ADMIN = '0x' + '66'.repeat(20) +const LOCKBOX = '0x' + '77'.repeat(20) +const NOT_THE_OWNER = '0x' + '88'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +/** An EVM remote pool, as the caller passes it (hex bytes) and as the chain reader returns it. */ +const REMOTE_POOL = '0x' + '99'.repeat(20) +/** Another remote pool, already registered on the lane in the duplicate tests. */ +const OTHER_REMOTE_POOL = '0x' + 'aa'.repeat(20) + +const SELECTOR = 5009297550715157269n // ethereum-mainnet +const SOLANA_SELECTOR = 16423721717087811551n // solana-devnet + +/** Calldata built from a fresh Interface — never the SDK's cached one, or this proves nothing. */ +const FRESH = new Interface([ + 'function addRemotePool(uint64 remoteChainSelector, bytes remotePoolAddress)', +]) +const expectedData = (remotePoolAddress = REMOTE_POOL, selector = SELECTOR) => + FRESH.encodeFunctionData('addRemotePool', [selector, remotePoolAddress]) + +/** The `getTokenPoolState` getters the owner gate reads, per version generation. */ +function poolReads(version: TokenPoolVersion, type: TokenPoolType, owner: string) { + if (version !== TokenPoolVersion.V2_0_0) + return { + getToken: [TOKEN], + owner: [owner], + getRouter: [ROUTER], + getRmnProxy: [RMN_PROXY], + getRateLimitAdmin: [RATE_LIMIT_ADMIN], + getSupportedChains: [[SELECTOR]], + } + return { + getToken: [TOKEN], + owner: [owner], + getRmnProxy: [RMN_PROXY], + getTokenDecimals: [18], + getSupportedChains: [[SELECTOR]], + getDynamicConfig: [ROUTER, RATE_LIMIT_ADMIN, RATE_LIMIT_ADMIN], + getAllowedFinalityConfig: [toBeHex(0, 4)], + ...(getTokenPoolFamily(type) === 'LockRelease' ? { getLockBox: [LOCKBOX] } : {}), + } +} + +type Calls = { typeAndVersion: number; remotes: Array<[string, bigint | undefined]>; calls: number } + +/** + * EVMChain stub: reports `type`/`version`, answers the owner-gate getters off the pool's own + * Interface, and returns (or throws for) one lane's remotes. + */ +function stubChain({ + type = 'BurnMintTokenPool', + version = TokenPoolVersion.V1_5_1, + owner = OWNER, + remotePools = [] as string[], + remotesError, + seen = { typeAndVersion: 0, remotes: [], calls: 0 }, +}: { + type?: TokenPoolType + version?: TokenPoolVersion + owner?: string + remotePools?: string[] + remotesError?: Error + seen?: Calls +} = {}): EVMChain { + const iface = TOKEN_POOL_INTERFACES[getTokenPoolFamily(type)][version] + const responses = new Map( + Object.entries(poolReads(version, type, owner)).map(([fn, values]) => [ + iface.getFunction(fn)!.selector, + iface.encodeFunctionResult(fn, values), + ]), + ) + const remote: TokenPoolRemote = { + remoteToken: TOKEN, + remotePools, + inboundRateLimiterState: null, + outboundRateLimiterState: null, + } + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + provider: { + call: ({ data }: { data: string }) => { + seen.calls++ + const encoded = responses.get(data.slice(0, 10)) + if (!encoded) + throw makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: null, data }, + invocation: null, + revert: null, + }) + return Promise.resolve(encoded) + }, + }, + typeAndVersion: () => { + seen.typeAndVersion++ + return Promise.resolve(parseTypeAndVersion(`${type} ${version}`)) + }, + getTokenInfo: () => Promise.resolve({ decimals: 18, symbol: 'TKN', name: 'Token' }), + getTokenPoolRemotes: (tokenPool: string, remoteChainSelector?: bigint) => { + seen.remotes.push([tokenPool, remoteChainSelector]) + return remotesError ? Promise.reject(remotesError) : Promise.resolve({ 'a-network': remote }) + }, + nextNonce: () => Promise.resolve(0), + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +function fakeSigner(waitError?: Error, address = OWNER) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => (waitError ? Promise.reject(waitError) : Promise.resolve({ status: 1 })), + }), + } +} + +const op = new AddRemotePool() + +function generate(chain: EVMChain, overrides: Partial = {}) { + return op.generate(chain, { + poolAddress: POOL, + remoteChainSelector: SELECTOR, + remotePoolAddress: REMOTE_POOL, + sender: OWNER, + ...overrides, + }) +} + +/** Versions that declare `addRemotePool`, each with both ABI families. */ +const SUPPORTED = [TokenPoolVersion.V1_5_1, TokenPoolVersion.V1_6_1, TokenPoolVersion.V2_0_0] +const TYPES: TokenPoolType[] = ['BurnMintTokenPool', 'LockReleaseTokenPool'] + +describe('AddRemotePool (cct/evm)', () => { + describe('generate', () => { + for (const version of SUPPORTED) { + for (const type of TYPES) { + it(`encodes addRemotePool(selector, bytes) for a ${type} ${version}`, async () => { + const unsigned = await generate(stubChain({ type, version })) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, POOL) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, expectedData()) + }) + } + } + + it('produces identical calldata for both ABI families', async () => { + const [burnMint, lockRelease] = await Promise.all( + TYPES.map(async (type) => (await generate(stubChain({ type }))).transactions[0]!.data), + ) + assert.equal(burnMint, lockRelease) + assert.equal(burnMint, expectedData()) + }) + + it('accepts a remotePoolAddress without the 0x prefix', async () => { + const unsigned = await generate(stubChain(), { + remotePoolAddress: REMOTE_POOL.slice(2).toUpperCase(), + }) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + }) + + it('encodes a 32-byte non-EVM remote pool address as-is', async () => { + const remotePoolAddress = '0x' + 'cd'.repeat(32) + const unsigned = await generate(stubChain({ remotePools: [] }), { + remoteChainSelector: SOLANA_SELECTOR, + remotePoolAddress, + }) + assert.equal(unsigned.transactions[0]!.data, expectedData(remotePoolAddress, SOLANA_SELECTOR)) + }) + + it('omits from, and skips the owner read, when sender is not supplied', async () => { + const seen: Calls = { typeAndVersion: 0, remotes: [], calls: 0 } + const unsigned = await generate(stubChain({ seen }), { sender: undefined }) + assert.equal(unsigned.transactions[0]!.from, undefined) + assert.equal(seen.calls, 0, 'no owner gate without a sender to compare') + }) + + it('scopes the remotes read to the one lane', async () => { + const seen: Calls = { typeAndVersion: 0, remotes: [], calls: 0 } + await generate(stubChain({ seen })) + assert.deepEqual(seen.remotes, [[POOL, SELECTOR]]) + }) + }) + + describe('validation', () => { + for (const [param, value] of [ + ['poolAddress', 'not-an-address'], + // a tx to `0x0` hits no code, so it mines as a successful no-op instead of reverting + ['poolAddress', ZeroAddress], + ['sender', 'not-an-address'], + ['remoteChainSelector', 1 as never], + ['remoteChainSelector', -1n], + ['remoteChainSelector', 2n ** 64n], + ['remotePoolAddress', ''], + ['remotePoolAddress', '0x'], + ['remotePoolAddress', '0xabc'], + ['remotePoolAddress', '0xzz'], + ['remotePoolAddress', 42 as never], + ] as const) { + it(`rejects ${param} = ${String(value)} before any RPC`, async () => { + const seen: Calls = { typeAndVersion: 0, remotes: [], calls: 0 } + await assert.rejects( + () => generate(stubChain({ seen }), { [param]: value }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'addRemotePool' && + err.context.param === param, + ) + assert.deepEqual([seen.typeAndVersion, seen.remotes.length, seen.calls], [0, 0, 0]) + }) + } + }) + + describe('version dispatch', () => { + it('is unsupported on v1.5.0, which has no additive primitive', async () => { + const seen: Calls = { typeAndVersion: 0, remotes: [], calls: 0 } + await assert.rejects( + () => generate(stubChain({ version: TokenPoolVersion.V1_5_0, seen })), + (err: unknown) => + err instanceof CCTOperationUnsupportedError && + err.context.operation === 'addRemotePool' && + err.context.version === '1.5.0', + ) + // the encoder is resolved off the single typeAndVersion read, before any further RPC + assert.deepEqual([seen.typeAndVersion, seen.remotes.length, seen.calls], [1, 0, 0]) + }) + + for (const version of SUPPORTED) { + it(`encodes on v${version}`, async () => { + const unsigned = await generate(stubChain({ version })) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + }) + } + + it('covers every known pool version', () => { + assert.deepEqual(Object.values(TokenPoolVersion), [TokenPoolVersion.V1_5_0, ...SUPPORTED]) + }) + }) + + describe('pre-transaction validation', () => { + it('rejects a remote pool already registered on the lane', async () => { + await assert.rejects( + () => generate(stubChain({ remotePools: [OTHER_REMOTE_POOL, REMOTE_POOL] })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'addRemotePool' && + err.context.param === 'remotePoolAddress', + ) + }) + + it('rejects a duplicate given as left-padded 32-byte bytes', async () => { + // the chain reader returns decoded 20-byte addresses; the caller may pass either form + await assert.rejects( + () => + generate(stubChain({ remotePools: [REMOTE_POOL] }), { + remotePoolAddress: zeroPadValue(REMOTE_POOL, 32), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'remotePoolAddress', + ) + }) + + it('rejects a duplicate whose registered spelling differs only in case', async () => { + await assert.rejects( + () => generate(stubChain({ remotePools: [REMOTE_POOL.toUpperCase().replace('0X', '0x')] })), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'remotePoolAddress', + ) + }) + + it('falls back to raw byte comparison when the remote family has no registered codec', async () => { + // `decodeAddress` only knows the families whose chain module is loaded (EVM always is); + // for anything else the undecoded hex is compared, so a duplicate is still caught + const bytes = '0x' + 'cd'.repeat(32) + await assert.rejects( + () => + generate(stubChain({ remotePools: [bytes] }), { + remoteChainSelector: SOLANA_SELECTOR, + remotePoolAddress: bytes.toUpperCase().replace('0X', '0x'), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'remotePoolAddress', + ) + }) + + it('allows adding to a lane that holds other remote pools', async () => { + const unsigned = await generate(stubChain({ remotePools: [OTHER_REMOTE_POOL] })) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + }) + + it('treats an unconfigured lane as having no remote pools', async () => { + const chain = stubChain({ + remotesError: new CCIPTokenPoolChainConfigNotFoundError(POOL, POOL, 'a-network'), + }) + const unsigned = await generate(chain) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + }) + + it('propagates any other remotes-read failure', async () => { + const boom = new Error('rpc down') + await assert.rejects(() => generate(stubChain({ remotesError: boom })), boom) + }) + + it('rejects a sender that is not the pool owner', async () => { + await assert.rejects( + () => generate(stubChain({ owner: NOT_THE_OWNER })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'addRemotePool' && + err.context.param === 'sender', + ) + }) + }) + + /** + * Regression guard for the owner gate on a v2.0.0 `SiloedLockReleaseTokenPool`. + * + * A siloed pool escrows per remote chain (`getLockBox(uint64)`, no no-arg `getLockBox()`), but + * it is a fully supported `TokenPoolType` and this op's calldata is perfectly valid against one. + * The gate must therefore admit it on the strength of `owner()` alone, without depending on any + * pool-shape detail that only the non-siloed variant reports. + */ + describe('siloed lock/release pools', () => { + const siloedPool = (owner = OWNER) => + stubChain({ type: 'SiloedLockReleaseTokenPool', version: TokenPoolVersion.V2_0_0, owner }) + + it('builds for a siloed lock/release pool, which getTokenPoolState cannot read', async () => { + const unsigned = await generate(siloedPool()) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, POOL) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, expectedData()) + }) + + it('still rejects a sender that is not the siloed pool owner', async () => { + await assert.rejects( + () => generate(siloedPool(NOT_THE_OWNER)), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'addRemotePool' && + err.context.param === 'sender', + ) + }) + }) + + describe('execute', () => { + const params = { + poolAddress: POOL, + remoteChainSelector: SELECTOR, + remotePoolAddress: REMOTE_POOL, + } + + it('signs and submits as the owner, resolving to the tx hash', async () => { + assert.deepEqual(await op.execute(stubChain(), { ...params, wallet: fakeSigner() }), { + hash: HASH, + }) + }) + + it('maps an on-chain revert to CCIPExecTxRevertedError', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + ...params, + wallet: fakeSigner(makeError('execution reverted', 'CALL_EXCEPTION')), + }), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && err.context.operation === 'addRemotePool', + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { ...params, wallet: {} }), + CCIPWalletInvalidError, + ) + }) + + it('rejects a sender that is not the signing wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { ...params, sender: NOT_THE_OWNER, wallet: fakeSigner() }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'addRemotePool' && + err.context.param === 'sender', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/add-remote-pool.ts b/ccip-sdk/src/cct/evm/token-pool/operations/add-remote-pool.ts new file mode 100644 index 000000000..8c53b3885 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/add-remote-pool.ts @@ -0,0 +1,128 @@ +/** + * addRemotePool: authorizes one more remote pool address on a lane (v1.5.1+). + * + * @remarks **v1.5.1 and newer.** v1.5.1 turned a lane's single remote pool into a set — several + * remote pools may be authorized at once, which is how a remote-side pool upgrade is rolled out + * without downtime (add the new pool, drain the old, then `removeRemotePool`). v1.5.0 has no + * additive primitive at all, only the wholesale {@link SetRemotePool}, so this op reports itself + * unsupported there rather than emulating an add as a replace. + * + * @packageDocumentation + */ + +import type { Interface } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { + TokenPoolVersion, + assertPoolOwner, + getTokenPoolInterface, + resolveEncoder, + resolveTokenPool, +} from '../contracts.ts' +import { + type ParsedRemotePoolParams, + type RemotePoolParams, + isRegisteredRemotePool, + parseRemotePoolParams, + readRegisteredRemotePools, +} from '../remote-pool.ts' + +/** + * Parameters for {@link AddRemotePool} — see {@link RemotePoolParams}; `remotePoolAddress` is the + * remote chain's pool address as hex bytes, added to the lane's existing set. + */ +export type AddRemotePoolParams = RemotePoolParams + +/** {@link AddRemotePoolParams} as {@link AddRemotePool.parse} leaves it. */ +type ParsedAddRemotePoolParams = ParsedRemotePoolParams + +/** Encodes `addRemotePool` calldata against the resolved pool {@link Interface}. */ +type Encoder = (iface: Interface, params: ParsedAddRemotePoolParams) => UnsignedEVMTx + +const encodeAddRemotePool: Encoder = ( + iface, + { poolAddress, remoteChainSelector, remotePoolAddress }, +) => + callTx( + poolAddress, + iface.encodeFunctionData('addRemotePool', [remoteChainSelector, remotePoolAddress]), + ) + +/** Authorizes an additional remote pool on one lane of a v1.5.1+ pool via `addRemotePool`. */ +export class AddRemotePool extends EVMOperation { + readonly name = 'addRemotePool' + + /** + * v1.5.1 and up, where the function was introduced and has not changed since — one entry + * covers v1.6.1 and v2.0.0 by {@link resolveEncoder}'s floor-match. No `null` ceiling is + * needed at the bottom: v1.5.0 matches nothing at or below itself and is reported unsupported + * for free. + */ + private readonly encoders: Partial> = { + [TokenPoolVersion.V1_5_1]: encodeAddRemotePool, + } + + /** + * Validates the pool address, lane selector and remote pool bytes before any RPC, keeping the + * parsed `remotePoolAddress` so {@link buildUnsigned} checks and encodes it without re-parsing. + */ + protected override parse(params: AddRemotePoolParams): ParsedAddRemotePoolParams { + return parseRemotePoolParams(this.name, params) + } + + /** + * Resolves the pool's version (rejecting v1.5.0), confirms `sender` owns the pool, then rejects + * a duplicate: the lane's currently registered remote pools are read scoped to this one + * selector, and an address already among them would revert on-chain + * (`PoolAlreadyAdded`). A lane that is not configured yet reads as having none — see + * {@link readRegisteredRemotePools}. + * @throws {@link CCTOperationUnsupportedError} if the pool is v1.5.0 + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the pool owner, or if + * `remotePoolAddress` is already registered on this lane + */ + protected async buildUnsigned( + chain: EVMChain, + params: ParsedAddRemotePoolParams, + ): Promise { + const { type, version } = await resolveTokenPool(chain, params.poolAddress) + // resolved before any further RPC, so an unsupported version fails on one call + const encode = resolveEncoder(this.encoders, version, this.name) + // owner-gated on-chain; surface it as a param error here instead of an on-chain revert + if (params.sender !== undefined) + await assertPoolOwner(this.name, chain, params.poolAddress, params.sender) + + const registered = await readRegisteredRemotePools(chain, params) + if (isRegisteredRemotePool(registered, params.remotePoolAddress, params.remoteChainSelector)) + throw new CCTParamsInvalidError( + this.name, + 'remotePoolAddress', + `is already registered on chain selector ${params.remoteChainSelector} (registered: ${registered.join(', ')}); adding it again reverts`, + ) + + chain.logger.debug( + `${this.name}: pool = ${params.poolAddress}, lane = ${params.remoteChainSelector}, registered = ${registered.length}`, + ) + return encode(getTokenPoolInterface(type, version), params) + } + + /** + * Signs and submits as the pool owner, defaulting `sender` to the signing wallet — the only + * address that can satisfy {@link buildUnsigned}'s owner check for a broadcast tx. See + * {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is rejected. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address, or + * if any other param is invalid (see {@link buildUnsigned}) + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/apply-allowlist-updates.test.ts b/ccip-sdk/src/cct/evm/token-pool/operations/apply-allowlist-updates.test.ts new file mode 100644 index 000000000..bf63d8147 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/apply-allowlist-updates.test.ts @@ -0,0 +1,408 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, getAddress, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { parseTypeAndVersion } from '../../../../utils.ts' +import { CCTOperationUnsupportedError, CCTParamsInvalidError } from '../../../errors.ts' +import { type TokenPoolFamily, TOKEN_POOL_INTERFACES, TokenPoolVersion } from '../contracts.ts' +import { + type ApplyAllowlistUpdatesParams, + ApplyAllowlistUpdates, +} from './apply-allowlist-updates.ts' + +const POOL = '0x' + '11'.repeat(20) +const OWNER = '0x' + '22'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +// Distinct fixtures per array: a swapped (removes, adds) pair must fail byte parity. +const ADDS = ['0x' + 'a1'.repeat(20), '0x' + 'a2'.repeat(20)] +const REMOVES = ['0x' + 'e1'.repeat(20)] + +/** Independent of the SDK's cached interfaces — the reference the encoding is measured against. */ +const REFERENCE = new Interface([ + 'function applyAllowListUpdates(address[] removes, address[] adds)', +]) +const DATA = REFERENCE.encodeFunctionData('applyAllowListUpdates', [REMOVES, ADDS]) + +/** Pool types reporting each ABI family, for the `typeAndVersion` the stub answers with. */ +const POOL_TYPE: Record = { + BurnMint: 'BurnMintTokenPool', + LockRelease: 'LockReleaseTokenPool', +} + +const LEGACY_VERSIONS = [ + TokenPoolVersion.V1_5_0, + TokenPoolVersion.V1_5_1, + TokenPoolVersion.V1_6_1, +] as const + +/** + * EVMChain stub: reports `type version` from `typeAndVersion`, and answers the pool's `owner()`, + * `getAllowListEnabled()` and `getAllowList()` `eth_call`s. Any other call reverts. `onCall` + * records that RPC happened at all, so the validation tests can assert nothing was issued. + * + * `allowlist` defaults to {@link REMOVES}, the set the default params remove from — leaving + * {@link ADDS} absent, so the default case is a real state change on both sides. + */ +function stubChain({ + family = 'BurnMint', + version = TokenPoolVersion.V1_5_1, + owner = OWNER, + allowlistEnabled = true, + allowlist = REMOVES, + onCall, +}: { + family?: TokenPoolFamily + version?: TokenPoolVersion + owner?: string + allowlistEnabled?: boolean + allowlist?: string[] + onCall?: () => void +} = {}): EVMChain { + const iface = TOKEN_POOL_INTERFACES[family][version] + const ownerSelector = iface.getFunction('owner')!.selector + // v2.0.0 dropped the allowlist getters, so only look them up where they exist + const allowlistEnabledSelector = iface.getFunction('getAllowListEnabled')?.selector + const allowlistSelector = iface.getFunction('getAllowList')?.selector + return { + provider: { + call: ({ data }: { data: string }) => { + onCall?.() + const selector = data.slice(0, 10) + if (selector === ownerSelector) + return Promise.resolve(iface.encodeFunctionResult('owner', [owner])) + if (selector === allowlistEnabledSelector) + return Promise.resolve( + iface.encodeFunctionResult('getAllowListEnabled', [allowlistEnabled]), + ) + if (selector === allowlistSelector) + return Promise.resolve(iface.encodeFunctionResult('getAllowList', [allowlist])) + throw makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: null, data }, + invocation: null, + revert: null, + }) + }, + }, + logger: { debug() {}, info() {}, warn() {}, error() {} }, + typeAndVersion: () => { + onCall?.() + return Promise.resolve(parseTypeAndVersion(`${POOL_TYPE[family]} ${version}`)) + }, + nextNonce: async () => 0, + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +function fakeSigner(waitError?: Error, address = OWNER) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => (waitError ? Promise.reject(waitError) : Promise.resolve({ status: 1 })), + }), + } +} + +const op = new ApplyAllowlistUpdates() + +function generate(chain: EVMChain, overrides: Partial = {}) { + return op.generate(chain, { + poolAddress: POOL, + removes: REMOVES, + adds: ADDS, + sender: OWNER, + ...overrides, + }) +} + +describe('ApplyAllowlistUpdates (cct/evm)', () => { + describe('generate', () => { + for (const version of LEGACY_VERSIONS) { + for (const family of ['BurnMint', 'LockRelease'] as const) { + it(`encodes applyAllowListUpdates(removes, adds) for a ${family} ${version} pool`, async () => { + const unsigned = await generate(stubChain({ family, version })) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, POOL) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, DATA) + }) + } + + it(`produces identical calldata for both ABI families at ${version}`, async () => { + const [burnMint, lockRelease] = await Promise.all([ + generate(stubChain({ family: 'BurnMint', version })), + generate(stubChain({ family: 'LockRelease', version })), + ]) + assert.equal(burnMint.transactions[0]!.data, lockRelease.transactions[0]!.data) + }) + } + + it('omits from, and skips the owner read, when sender is not supplied', async () => { + let calls = 0 + const unsigned = await generate(stubChain({ onCall: () => calls++ }), { sender: undefined }) + assert.equal(unsigned.transactions[0]!.from, undefined) + assert.equal(unsigned.transactions[0]!.data, DATA) + // typeAndVersion + the two allowlist reads — no owner() read without a sender to compare + // it against; the allowlist pre-flight does not depend on the signer and still runs + assert.equal(calls, 3) + }) + + it('encodes an empty removes array (adds only)', async () => { + const unsigned = await generate(stubChain(), { removes: [] }) + assert.equal( + unsigned.transactions[0]!.data, + REFERENCE.encodeFunctionData('applyAllowListUpdates', [[], ADDS]), + ) + }) + + it('encodes an empty adds array (removes only)', async () => { + const unsigned = await generate(stubChain(), { adds: [] }) + assert.equal( + unsigned.transactions[0]!.data, + REFERENCE.encodeFunctionData('applyAllowListUpdates', [REMOVES, []]), + ) + }) + + it('rejects a sender that is not the pool owner', async () => { + await assert.rejects( + () => generate(stubChain(), { sender: '0x' + '99'.repeat(20) }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyAllowlistUpdates' && + err.context.param === 'sender', + ) + }) + }) + + describe('validation', () => { + const cases: { name: string; param: string; params: Partial }[] = [ + { name: 'an invalid poolAddress', param: 'poolAddress', params: { poolAddress: 'nope' } }, + // a tx to `0x0` hits no code, so it would mine as a successful no-op rather than reverting + { name: 'the zero poolAddress', param: 'poolAddress', params: { poolAddress: ZeroAddress } }, + // `.map` skips holes, so without the density guard a sparse array validated clean and the + // hole reached ethers as `undefined` + { + name: 'a hole in adds', + param: 'adds[1]', + params: { + adds: (() => { + const sparse = [ADDS[0]!] + sparse[2] = ADDS[1] ?? ZeroAddress + return sparse + })(), + }, + }, + { + name: 'a hole in removes', + param: 'removes[1]', + params: { + removes: (() => { + const sparse = [REMOVES[0]!] + sparse[2] = REMOVES[0]! + return sparse + })(), + }, + }, + { + name: 'an invalid address inside adds', + param: 'adds[1]', + params: { adds: [ADDS[0]!, 'not-an-address'] }, + }, + { + name: 'an invalid address inside removes', + param: 'removes[0]', + params: { removes: ['not-an-address'] }, + }, + { + name: 'a missing removes', + param: 'removes', + params: { removes: undefined }, + }, + { name: 'a non-array adds', param: 'adds', params: { adds: 42 as unknown as string[] } }, + { name: 'both arrays empty', param: 'adds', params: { removes: [], adds: [] } }, + { + name: 'duplicates within adds', + param: 'adds', + params: { adds: [ADDS[0]!, ADDS[0]!] }, + }, + { + name: 'duplicates within removes, differing only in case', + param: 'removes', + params: { removes: [REMOVES[0]!, getAddress(REMOVES[0]!)] }, + }, + { + name: 'an address present in both adds and removes', + param: 'adds', + params: { adds: [ADDS[0]!], removes: [ADDS[0]!] }, + }, + { name: 'an invalid sender', param: 'sender', params: { sender: 'not-an-address' } }, + // the pool `continue`s past a zero address in adds, and can therefore never hold one: + // a silent no-op on either side, so it is rejected locally rather than encoded + { + name: 'the zero address inside adds', + param: 'adds[0]', + params: { adds: [ZeroAddress] }, + }, + { + name: 'the zero address inside removes', + param: 'removes[0]', + params: { removes: [ZeroAddress] }, + }, + ] + + for (const { name, param, params } of cases) { + it(`rejects ${name} before any RPC`, async () => { + let calls = 0 + await assert.rejects( + () => generate(stubChain({ onCall: () => calls++ }), params), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyAllowlistUpdates' && + err.context.param === param, + ) + assert.equal(calls, 0) + }) + } + }) + + describe('allowlist pre-flight', () => { + it('rejects a pool deployed without an allowlist', async () => { + await assert.rejects( + () => generate(stubChain({ allowlistEnabled: false, allowlist: [] })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyAllowlistUpdates' && + err.context.param === 'poolAddress', + ) + }) + + it('rejects removing an address that is not currently allowlisted', async () => { + // the pool's EnumerableSet.remove would return false and the tx would change nothing + await assert.rejects( + () => generate(stubChain({ allowlist: [ADDS[0]!] }), { adds: [] }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyAllowlistUpdates' && + err.context.param === 'removes', + ) + }) + + it('rejects adding an address that is already allowlisted', async () => { + await assert.rejects( + () => generate(stubChain({ allowlist: [...REMOVES, ADDS[1]!] })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyAllowlistUpdates' && + err.context.param === 'adds', + ) + }) + + it('matches the on-chain allowlist case-insensitively', async () => { + const unsigned = await generate( + stubChain({ allowlist: REMOVES.map((address) => address.toLowerCase()) }), + ) + assert.equal(unsigned.transactions[0]!.data, DATA) + }) + + it('accepts an empty allowlist when the feature is enabled (adds only)', async () => { + const unsigned = await generate(stubChain({ allowlist: [] }), { removes: [] }) + assert.equal( + unsigned.transactions[0]!.data, + REFERENCE.encodeFunctionData('applyAllowListUpdates', [[], ADDS]), + ) + }) + }) + + describe('execute', () => { + const params = { poolAddress: POOL, removes: REMOVES, adds: ADDS } + + it('signs and submits, resolving to the tx hash', async () => { + assert.deepEqual(await op.execute(stubChain(), { ...params, wallet: fakeSigner() }), { + hash: HASH, + }) + }) + + it('maps an on-chain revert to CCIPExecTxRevertedError', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + ...params, + wallet: fakeSigner(makeError('execution reverted', 'CALL_EXCEPTION')), + }), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && + err.context.operation === 'applyAllowlistUpdates', + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { ...params, wallet: {} }), + CCIPWalletInvalidError, + ) + }) + + it('rejects a sender that diverges from the signing wallet', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + ...params, + sender: '0x' + '99'.repeat(20), + wallet: fakeSigner(), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects a signing wallet that is not the pool owner', async () => { + const notOwner = '0x' + '99'.repeat(20) + await assert.rejects( + () => op.execute(stubChain(), { ...params, wallet: fakeSigner(undefined, notOwner) }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyAllowlistUpdates' && + err.context.param === 'sender', + ) + }) + }) + + describe('version dispatch', () => { + for (const version of LEGACY_VERSIONS) { + it(`supports ${version}`, async () => { + const unsigned = await generate(stubChain({ version })) + assert.equal(unsigned.transactions[0]!.data, DATA) + }) + } + + it('rejects 2.0.0, where the allowlist was removed from the contract', async () => { + await assert.rejects( + () => generate(stubChain({ version: TokenPoolVersion.V2_0_0 })), + (err: unknown) => + err instanceof CCTOperationUnsupportedError && + err.context.operation === 'applyAllowlistUpdates' && + err.context.version === TokenPoolVersion.V2_0_0, + ) + }) + + it('covers every known TokenPoolVersion', () => { + assert.deepEqual(Object.values(TokenPoolVersion), [ + ...LEGACY_VERSIONS, + TokenPoolVersion.V2_0_0, + ]) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/apply-allowlist-updates.ts b/ccip-sdk/src/cct/evm/token-pool/operations/apply-allowlist-updates.ts new file mode 100644 index 000000000..e89a5cec5 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/apply-allowlist-updates.ts @@ -0,0 +1,261 @@ +/** + * applyAllowlistUpdates — replaces entries in a token pool's sender allowlist, the set of local + * addresses permitted to initiate a CCIP transfer through the pool. Removes are applied before + * adds, in one call: `applyAllowListUpdates(address[] removes, address[] adds)`. + * + * @remarks Requires the pool to have been deployed *with* an allowlist: `allowlistEnabled` is + * immutable, and the call reverts `AllowListNotEnabled` when it is false. That, and every update + * the pool would silently ignore, is pre-flighted against the current allowlist before any + * calldata is built. + * + * @remarks Available on v1.5.0–v1.6.1 with an unchanged signature, and **removed outright in + * v2.0.0**, which has no allowlist. The encoder table pins an explicit `null` ceiling at + * {@link TokenPoolVersion.V2_0_0} so {@link resolveEncoder}'s floor-match cannot inherit the + * 1.5.0 encoder upward and emit calldata for a selector the pool does not implement; a 2.0.0 pool + * reports {@link CCTOperationUnsupportedError} instead. + * + * @packageDocumentation + */ + +import { type Interface, getAddress } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { validateArray, validateNonZeroAddress } from '../../validate.ts' +import { + TokenPoolVersion, + assertPoolOwner, + getTokenPoolInterface, + readTokenPoolAllowlist, + resolveEncoder, + resolveTokenPool, +} from '../contracts.ts' + +/** Parameters for {@link ApplyAllowlistUpdates}. */ +export type ApplyAllowlistUpdatesParams = { + /** Token pool contract address whose allowlist is being updated. */ + poolAddress: string + /** + * Addresses to remove from the allowlist. Applied *before* {@link adds} on-chain. Must contain + * no duplicates, no zero address, and no address that also appears in {@link adds}. Every entry + * must currently be allowlisted — the pool silently ignores the rest. + */ + removes: string[] + /** + * Addresses to add to the allowlist. Must contain no duplicates, no zero address, and no + * address that also appears in {@link removes}. No entry may already be allowlisted — the pool + * silently ignores the rest. + */ + adds: string[] + /** + * Current pool owner; sets `tx.from` for offline / multisig signing. When supplied it is also + * checked against the pool's on-chain `owner()` before any calldata is built, since the pool + * gates `applyAllowListUpdates` on `onlyOwner`. + */ + sender?: string +} + +/** + * Normalized params for {@link ApplyAllowlistUpdates}: every allowlist entry checksummed and + * duplicate-free, so {@link buildUnsigned} and the encoder never re-derive them. + */ +type ParsedApplyAllowlistUpdatesParams = { + poolAddress: string + removes: string[] + adds: string[] + sender?: string +} + +/** Encodes `applyAllowListUpdates` calldata against the resolved pool {@link Interface}. */ +type Encoder = (iface: Interface, params: ParsedApplyAllowlistUpdatesParams) => UnsignedEVMTx + +/** + * `removes` FIRST, then `adds` — the ABI's own parameter order. A swapped pair still encodes and + * still type-checks (both are `address[]`), and would silently allowlist the addresses meant to be + * revoked, so the byte-parity test is what pins this down. + */ +const encodeApplyAllowlistUpdates: Encoder = (iface, { poolAddress, removes, adds }) => + callTx(poolAddress, iface.encodeFunctionData('applyAllowListUpdates', [removes, adds])) + +/** + * Validates every entry of one array and returns it checksummed, rejecting duplicates. Compared + * on checksummed form, so the same address in two different casings still counts as a duplicate. + * + * The zero address is rejected outright: the pool skips it in `adds` (`if (toAdd == address(0)) + * continue`) and can never hold it, so it is a silent no-op in either array. + * @throws {@link CCTParamsInvalidError} if an entry is not a valid address or is the zero address + * (reported as `param[i]`), or the array holds duplicates + */ +function normalizeAddresses(operation: string, param: string, addresses: string[]): string[] { + const normalized = addresses.map((address, i) => { + validateNonZeroAddress(operation, `${param}[${i}]`, address) + return getAddress(address) + }) + if (new Set(normalized).size !== normalized.length) + throw new CCTParamsInvalidError(operation, param, 'must not contain duplicate addresses') + return normalized +} + +/** + * Applies allowlist removals and additions to an EVM token pool in one `applyAllowListUpdates` + * call (v1.5.0–v1.6.1; unsupported on v2.0.0, which has no allowlist). + */ +export class ApplyAllowlistUpdates extends EVMOperation< + ApplyAllowlistUpdatesParams, + ParsedApplyAllowlistUpdatesParams +> { + readonly name = 'applyAllowlistUpdates' + + /** + * One entry at V1_5_0 covers v1.5.0/v1.5.1/v1.6.1, whose signature is identical, and the + * explicit `null` at V2_0_0 stops the floor-match walk: the function was removed from the + * contract there, so there is nothing to inherit. See {@link resolveEncoder}. + */ + private readonly encoders: Partial> = { + [TokenPoolVersion.V1_5_0]: encodeApplyAllowlistUpdates, + [TokenPoolVersion.V2_0_0]: null, + } + + /** + * Validates the pool address and every allowlist entry before any RPC, *keeping* what each + * check produced (checksummed, duplicate-free arrays) so {@link buildUnsigned} and the encoder + * never re-derive it. + * + * Three judgement calls, all rejections: + * - **both arrays empty** — rejected: such a call encodes and mines while changing nothing, so + * it can only be a caller bug; mirrors `lockbox/operations/authorize-callers.ts`. + * - **duplicates within an array** — rejected, mirroring the Solana `configureAllowlist` / + * `removeFromAllowlist` ops. The EVM pool treats its allowlist as a set, so a duplicate is a + * silent no-op on-chain; catching it locally keeps the two families' contracts identical. + * - **an address in BOTH `adds` and `removes`** — rejected: removes apply first, so the address + * would end up *allowlisted*, and no caller can reasonably have meant both. + * + * - **the zero address in either array** — rejected: the pool `continue`s past it in `adds` and + * so can never hold it, making it a silent no-op on either side. + * + * Comparisons are on checksummed form, so the same address in two different casings still + * counts as a duplicate / an overlap. The remaining no-ops — removing an address that is not + * allowlisted, adding one that already is — need the pool's current allowlist and are caught in + * {@link buildUnsigned}. + * @throws {@link CCTParamsInvalidError} if `poolAddress` is invalid, either array is not an + * array or is sparse, both are empty, an entry is not a valid address or is the zero address + * (reported as `adds[i]` / `removes[i]`), an array holds duplicates, or an address appears in + * both arrays + */ + protected override parse(params: ApplyAllowlistUpdatesParams): ParsedApplyAllowlistUpdatesParams { + validateNonZeroAddress(this.name, 'poolAddress', params.poolAddress) + validateArray(this.name, 'removes', params.removes) + validateArray(this.name, 'adds', params.adds) + if (params.removes.length + params.adds.length === 0) { + throw new CCTParamsInvalidError( + this.name, + 'adds', + 'at least one address must be added or removed', + ) + } + + const removes = normalizeAddresses(this.name, 'removes', params.removes) + const adds = normalizeAddresses(this.name, 'adds', params.adds) + const removed = new Set(removes) + const overlap = adds.find((address) => removed.has(address)) + if (overlap !== undefined) { + throw new CCTParamsInvalidError( + this.name, + 'adds', + `${overlap} is also in removes; removes are applied first on-chain, so it would end up allowlisted — list it in one array only`, + ) + } + return { poolAddress: params.poolAddress, removes, adds, sender: params.sender } + } + + /** + * Resolves the pool's type + version, floor-matches the encoder (rejecting v2.0.0, which has no + * allowlist), confirms `sender` owns the pool when it is known, then pre-flights the update + * against the pool's current allowlist so nothing that would revert or mine as a no-op is ever + * built. + * + * Three state preconditions, all read in one round-trip by {@link readTokenPoolAllowlist}: + * - **the allowlist must be enabled** — `applyAllowListUpdates` opens with + * `if (!i_allowlistEnabled) revert AllowListNotEnabled()`. The flag is `immutable`, set to + * `allowlist.length > 0` in the constructor, so a pool deployed without one can never gain + * it: this is a permanent property of the pool, not a transient state. + * - **every `removes` entry must currently be allowlisted** — `EnumerableSet.remove` returns + * false for an absent address and the pool ignores it, so the tx mines having changed + * nothing. Mirrors `remove-remote-pool.ts`. + * - **no `adds` entry may already be allowlisted** — the symmetric case: `EnumerableSet.add` + * returns false and the entry is silently skipped. + * + * @remarks Encoder resolution runs *before* the owner read on purpose: an unsupported version + * should surface as {@link CCTOperationUnsupportedError} rather than burning an RPC on a pool + * this op can never target. The owner check is skipped entirely when `sender` is omitted — + * there is nothing to compare against, and `generateUnsignedApplyAllowlistUpdates` is expected + * to be usable before the eventual signer is known. {@link execute} always supplies one. The + * allowlist pre-flight, by contrast, does not depend on the signer and always runs. + * @throws {@link CCTOperationUnsupportedError} if the pool is v2.0.0 + * @throws {@link CCTContractTypeInvalidError} if the address is not a supported pool type + * @throws {@link CCTContractVersionUnsupportedError} if the pool reports an unknown version + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the pool owner, if the + * pool has no allowlist enabled, if a `removes` entry is not currently allowlisted, or if an + * `adds` entry already is + */ + protected async buildUnsigned( + chain: EVMChain, + params: ParsedApplyAllowlistUpdatesParams, + ): Promise { + const { type, version } = await resolveTokenPool(chain, params.poolAddress) + // resolved before any further RPC, so an unsupported version fails on one call + const encode = resolveEncoder(this.encoders, version, this.name) + // owner-gated on-chain; surface it as a param error here instead of an on-chain revert + if (params.sender !== undefined) + await assertPoolOwner(this.name, chain, params.poolAddress, params.sender) + + const { enabled, entries } = await readTokenPoolAllowlist(chain, params.poolAddress) + if (!enabled) + throw new CCTParamsInvalidError( + this.name, + 'poolAddress', + 'pool was deployed without an allowlist and can never have one (`allowlistEnabled` is immutable and false); applyAllowListUpdates reverts AllowListNotEnabled', + ) + + const allowlisted = new Set(entries) + const absent = params.removes.find((address) => !allowlisted.has(address)) + if (absent !== undefined) + throw new CCTParamsInvalidError( + this.name, + 'removes', + `${absent} is not allowlisted (allowlisted: ${entries.join(', ') || 'none'}); the pool would ignore it and the tx would change nothing`, + ) + const present = params.adds.find((address) => allowlisted.has(address)) + if (present !== undefined) + throw new CCTParamsInvalidError( + this.name, + 'adds', + `${present} is already allowlisted; the pool would ignore it and the tx would change nothing`, + ) + + chain.logger.debug( + `${this.name}: pool = ${params.poolAddress}, allowlisted = ${entries.length}, removes = ${params.removes.length}, adds = ${params.adds.length}`, + ) + return encode(getTokenPoolInterface(type, version), params) + } + + /** + * Signs and submits as the pool owner, defaulting `sender` to the signing wallet — the only + * address that can satisfy {@link buildUnsigned}'s owner check for a broadcast tx. See + * {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is rejected rather + * than signed. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address, + * if the wallet is not the pool owner, or if any other param is invalid + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/apply-chain-updates.test.ts b/ccip-sdk/src/cct/evm/token-pool/operations/apply-chain-updates.test.ts new file mode 100644 index 000000000..e0da0dde8 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/apply-chain-updates.test.ts @@ -0,0 +1,927 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError, toBeHex } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { parseTypeAndVersion } from '../../../../utils.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { type TokenPoolFamily, TOKEN_POOL_INTERFACES, TokenPoolVersion } from '../contracts.ts' +import { type ApplyChainUpdatesParams, ApplyChainUpdates } from './apply-chain-updates.ts' + +const POOL = '0x' + '11'.repeat(20) +const TOKEN = '0x' + '22'.repeat(20) +const ROUTER = '0x' + '33'.repeat(20) +const OWNER = '0x' + '44'.repeat(20) +const RMN_PROXY = '0x' + '55'.repeat(20) +const RATE_LIMIT_ADMIN = '0x' + '66'.repeat(20) +const FEE_ADMIN = '0x' + '77'.repeat(20) +const LOCKBOX = '0x' + '88'.repeat(20) +const NOT_OWNER = '0x' + '99'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +const SEL_A = 16015286601757825753n // ethereum-sepolia +const SEL_B = 3478487238524512106n // arbitrum-sepolia +const REMOTE_TOKEN = '0x' + 'aa'.repeat(20) +const REMOTE_POOL_1 = '0x' + 'bb'.repeat(20) +const REMOTE_POOL_2 = '0x' + 'cc'.repeat(32) // a 32-byte (non-EVM) remote pool + +const INBOUND = { enabled: true, capacity: 100_000n, rate: 167n } as const +const OUTBOUND = { enabled: false } as const + +/** Both directions as the ABI spells them — `isEnabled`, with the disabled amounts defaulted. */ +const ABI_INBOUND = { isEnabled: true, capacity: 100_000n, rate: 167n } +const ABI_OUTBOUND = { isEnabled: false, capacity: 0n, rate: 0n } + +/** + * Expected calldata is built from interfaces declared *here*, from the human-readable signatures + * read off the vendored ABIs — not from the SDK's own cached `TOKEN_POOL_INTERFACES`, which would + * make the parity assertions circular. + */ +const FRESH_V1_5_0 = new Interface([ + 'function applyChainUpdates((uint64 remoteChainSelector, bool allowed, bytes remotePoolAddress, bytes remoteTokenAddress, (bool isEnabled, uint128 capacity, uint128 rate) outboundRateLimiterConfig, (bool isEnabled, uint128 capacity, uint128 rate) inboundRateLimiterConfig)[] chains)', +]) +const FRESH_V1_5_1 = new Interface([ + 'function applyChainUpdates(uint64[] remoteChainSelectorsToRemove, (uint64 remoteChainSelector, bytes[] remotePoolAddresses, bytes remoteTokenAddress, (bool isEnabled, uint128 capacity, uint128 rate) outboundRateLimiterConfig, (bool isEnabled, uint128 capacity, uint128 rate) inboundRateLimiterConfig)[] chainsToAdd)', +]) + +const DATA_V1_5_0 = FRESH_V1_5_0.encodeFunctionData('applyChainUpdates', [ + [ + { + remoteChainSelector: SEL_A, + allowed: true, + remotePoolAddress: REMOTE_POOL_1, + remoteTokenAddress: REMOTE_TOKEN, + outboundRateLimiterConfig: ABI_OUTBOUND, + inboundRateLimiterConfig: ABI_INBOUND, + }, + { + remoteChainSelector: SEL_B, + allowed: false, + remotePoolAddress: REMOTE_POOL_1, + remoteTokenAddress: REMOTE_TOKEN, + outboundRateLimiterConfig: ABI_OUTBOUND, + inboundRateLimiterConfig: ABI_OUTBOUND, + }, + ], +]) + +const DATA_V1_5_1 = FRESH_V1_5_1.encodeFunctionData('applyChainUpdates', [ + [SEL_B], + [ + { + remoteChainSelector: SEL_A, + remotePoolAddresses: [REMOTE_POOL_1, REMOTE_POOL_2], + remoteTokenAddress: REMOTE_TOKEN, + outboundRateLimiterConfig: ABI_OUTBOUND, + inboundRateLimiterConfig: ABI_INBOUND, + }, + ], +]) + +/** The v1.5.0 params whose expected calldata is {@link DATA_V1_5_0}. */ +function paramsV1_5_0(overrides: Record = {}): ApplyChainUpdatesParams { + return { + version: TokenPoolVersion.V1_5_0, + poolAddress: POOL, + sender: OWNER, + chains: [ + { + remoteChainSelector: SEL_A, + allowed: true, + remoteTokenAddress: REMOTE_TOKEN, + remotePoolAddress: REMOTE_POOL_1, + inboundRateLimiterConfig: INBOUND, + outboundRateLimiterConfig: OUTBOUND, + }, + { + remoteChainSelector: SEL_B, + allowed: false, + remoteTokenAddress: REMOTE_TOKEN, + remotePoolAddress: REMOTE_POOL_1, + inboundRateLimiterConfig: OUTBOUND, + outboundRateLimiterConfig: OUTBOUND, + }, + ], + ...overrides, + } +} + +/** The v1.5.1+ params whose expected calldata is {@link DATA_V1_5_1}. */ +function paramsV1_5_1(overrides: Record = {}): ApplyChainUpdatesParams { + return { + version: TokenPoolVersion.V1_5_1, + poolAddress: POOL, + sender: OWNER, + remoteChainSelectorsToRemove: [SEL_B], + chainsToAdd: [ + { + remoteChainSelector: SEL_A, + remoteTokenAddress: REMOTE_TOKEN, + remotePoolAddresses: [REMOTE_POOL_1, REMOTE_POOL_2], + inboundRateLimiterConfig: INBOUND, + outboundRateLimiterConfig: OUTBOUND, + }, + ], + ...overrides, + } +} + +/** Pool contract type reported per ABI family, both of which exist at every supported version. */ +const POOL_TYPE: Record = { + BurnMint: 'BurnMintTokenPool', + LockRelease: 'LockReleaseTokenPool', +} + +/** + * The `owner()`/getter results `GetTokenPoolState` reads, encoded per version generation: v2.0.0 + * folds router + both admin roles into `getDynamicConfig` and adds the finality window, where the + * legacy versions have standalone getters. + */ +function poolStateReads(version: TokenPoolVersion, family: TokenPoolFamily): Map { + const responses = new Map() + const iface = TOKEN_POOL_INTERFACES[family][version] + const add = (fn: string, values: unknown[]) => + responses.set(iface.getFunction(fn)!.selector, iface.encodeFunctionResult(fn, values)) + + add('getToken', [TOKEN]) + add('owner', [OWNER]) + add('getRmnProxy', [RMN_PROXY]) + add('getSupportedChains', [[SEL_A]]) + if (version === TokenPoolVersion.V2_0_0) { + add('getTokenDecimals', [18]) + add('getDynamicConfig', [ROUTER, RATE_LIMIT_ADMIN, FEE_ADMIN]) + add('getAllowedFinalityConfig', [toBeHex(0, 4)]) + if (family === 'LockRelease') add('getLockBox', [LOCKBOX]) + } else { + add('getRouter', [ROUTER]) + add('getRateLimitAdmin', [RATE_LIMIT_ADMIN]) + } + return responses +} + +type Stub = { + chain: EVMChain + /** How many times the op probed `typeAndVersion` — the first RPC any build makes. */ + probes: () => number +} + +/** + * EVMChain stub: reports `typeAndVersion` for the requested family/version and answers the pool's + * own state getters off `eth_call`. Any other getter reverts. + */ +function stubChain( + version: TokenPoolVersion = TokenPoolVersion.V1_5_1, + family: TokenPoolFamily = 'BurnMint', + owner = OWNER, +): Stub { + let probes = 0 + const responses = poolStateReads(version, family) + if (owner !== OWNER) { + const iface = TOKEN_POOL_INTERFACES[family][version] + responses.set( + iface.getFunction('owner')!.selector, + iface.encodeFunctionResult('owner', [owner]), + ) + } + const chain = { + provider: { + call: ({ data }: { data: string }) => { + const encoded = responses.get(data.slice(0, 10)) + if (!encoded) + throw makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: null, data }, + invocation: null, + revert: null, + }) + return Promise.resolve(encoded) + }, + }, + logger: { debug() {}, info() {}, warn() {}, error() {} }, + typeAndVersion: () => { + probes++ + return Promise.resolve(parseTypeAndVersion(`${POOL_TYPE[family]} ${version}`)) + }, + getTokenInfo: () => Promise.resolve({ decimals: 18, symbol: 'TKN', name: 'Token' }), + nextNonce: () => Promise.resolve(0), + rollbackNonce: () => {}, + } as unknown as EVMChain + return { chain, probes: () => probes } +} + +function fakeSigner(waitError?: Error, address = OWNER) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => (waitError ? Promise.reject(waitError) : Promise.resolve({ status: 1 })), + }), + } +} + +const op = new ApplyChainUpdates() + +/** Every supported pool version, paired with the parameter shape and calldata it expects. */ +const DISPATCH = [ + { + version: TokenPoolVersion.V1_5_0, + params: paramsV1_5_0, + data: DATA_V1_5_0, + otherParams: paramsV1_5_1, + }, + { + version: TokenPoolVersion.V1_5_1, + params: paramsV1_5_1, + data: DATA_V1_5_1, + otherParams: paramsV1_5_0, + }, + { + version: TokenPoolVersion.V1_6_1, + params: paramsV1_5_1, + data: DATA_V1_5_1, + otherParams: paramsV1_5_0, + }, + { + version: TokenPoolVersion.V2_0_0, + params: paramsV1_5_1, + data: DATA_V1_5_1, + otherParams: paramsV1_5_0, + }, +] as const + +describe('ApplyChainUpdates (cct/evm)', () => { + describe('generate', () => { + for (const { version, params, data } of DISPATCH) { + for (const family of ['BurnMint', 'LockRelease'] as const) { + it(`encodes applyChainUpdates for a v${version} ${family} pool`, async () => { + const { chain } = stubChain(version, family) + const unsigned = await op.generate(chain, params()) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, POOL) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, data) + }) + } + + it(`encodes identical calldata for both ABI families at v${version}`, async () => { + const burnMint = await op.generate(stubChain(version, 'BurnMint').chain, params()) + const lockRelease = await op.generate(stubChain(version, 'LockRelease').chain, params()) + assert.equal(burnMint.transactions[0]!.data, lockRelease.transactions[0]!.data) + assert.equal(burnMint.transactions[0]!.data, data) + }) + } + + it('omits from when sender is not supplied, and skips the owner probe', async () => { + const { chain } = stubChain(TokenPoolVersion.V1_5_1, 'BurnMint', NOT_OWNER) + // owner() reports NOT_OWNER, so this only builds because no sender was given to check + const unsigned = await op.generate(chain, paramsV1_5_1({ sender: undefined })) + assert.equal(unsigned.transactions[0]!.from, undefined) + assert.equal(unsigned.transactions[0]!.data, DATA_V1_5_1) + }) + + it('normalises 0x-less and upper-case hex remote addresses', async () => { + const { chain } = stubChain() + const unsigned = await op.generate( + chain, + paramsV1_5_1({ + chainsToAdd: [ + { + remoteChainSelector: SEL_A, + remoteTokenAddress: 'AA'.repeat(20), + remotePoolAddresses: ['0X' + 'BB'.repeat(20), REMOTE_POOL_2], + inboundRateLimiterConfig: INBOUND, + outboundRateLimiterConfig: OUTBOUND, + }, + ], + }), + ) + assert.equal(unsigned.transactions[0]!.data, DATA_V1_5_1) + }) + + it('accepts uint128 max for both rate-limit amounts', async () => { + // the widest legal RateLimiter.Config; rate === capacity, so only a v1.6.1+ pool takes it + const UINT128_MAX = 2n ** 128n - 1n + const unsigned = await op.generate( + stubChain(TokenPoolVersion.V1_6_1).chain, + paramsV1_5_1({ + remoteChainSelectorsToRemove: [], + chainsToAdd: [ + { + ...paramsV1_5_1AddEntry(), + inboundRateLimiterConfig: { + enabled: true, + capacity: UINT128_MAX, + rate: UINT128_MAX, + }, + }, + ], + }), + ) + assert.equal( + unsigned.transactions[0]!.data, + FRESH_V1_5_1.encodeFunctionData('applyChainUpdates', [ + [], + [ + { + remoteChainSelector: SEL_A, + remotePoolAddresses: [REMOTE_POOL_1], + remoteTokenAddress: REMOTE_TOKEN, + outboundRateLimiterConfig: ABI_OUTBOUND, + inboundRateLimiterConfig: { + isEnabled: true, + capacity: UINT128_MAX, + rate: UINT128_MAX, + }, + }, + ], + ]), + ) + }) + + it('rejects a sender that is not the pool owner', async () => { + const { chain } = stubChain(TokenPoolVersion.V1_5_1, 'BurnMint', NOT_OWNER) + await assert.rejects( + () => op.generate(chain, paramsV1_5_1()), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyChainUpdates' && + err.context.param === 'sender', + ) + }) + }) + + describe('validation', () => { + const cases: [string, ApplyChainUpdatesParams][] = [ + ['poolAddress', paramsV1_5_1({ poolAddress: 'not-an-address' })], + ['sender', paramsV1_5_1({ sender: 'not-an-address' })], + ['version', paramsV1_5_1({ version: '1.6.1' })], + ['chainsToAdd', paramsV1_5_1({ chainsToAdd: 'nope' })], + ['remoteChainSelectorsToRemove', paramsV1_5_1({ remoteChainSelectorsToRemove: 'nope' })], + ['chainsToAdd', paramsV1_5_1({ chainsToAdd: [], remoteChainSelectorsToRemove: [] })], + ['remoteChainSelectorsToRemove[0]', paramsV1_5_1({ remoteChainSelectorsToRemove: [1] })], + ['chainsToAdd[0]', paramsV1_5_1({ chainsToAdd: [null] })], + [ + 'chainsToAdd[0].remoteChainSelector', + paramsV1_5_1({ + chainsToAdd: [{ ...paramsV1_5_1AddEntry(), remoteChainSelector: -1n }], + }), + ], + [ + 'chainsToAdd[0].remotePoolAddresses', + paramsV1_5_1({ chainsToAdd: [{ ...paramsV1_5_1AddEntry(), remotePoolAddresses: [] }] }), + ], + [ + 'chainsToAdd[0].remotePoolAddresses[0]', + paramsV1_5_1({ + chainsToAdd: [{ ...paramsV1_5_1AddEntry(), remotePoolAddresses: ['0xabc'] }], + }), + ], + [ + 'chainsToAdd[0].remotePoolAddresses[1]', + paramsV1_5_1({ + chainsToAdd: [ + { + ...paramsV1_5_1AddEntry(), + remotePoolAddresses: [REMOTE_POOL_1, '0X' + 'BB'.repeat(20)], + }, + ], + }), + ], + [ + 'chainsToAdd[0].remoteTokenAddress', + paramsV1_5_1({ chainsToAdd: [{ ...paramsV1_5_1AddEntry(), remoteTokenAddress: '' }] }), + ], + [ + 'chainsToAdd[0].inboundRateLimiterConfig.enabled', + paramsV1_5_1({ + chainsToAdd: [{ ...paramsV1_5_1AddEntry(), inboundRateLimiterConfig: {} }], + }), + ], + [ + 'chainsToAdd[0].outboundRateLimiterConfig.rate', + paramsV1_5_1({ + chainsToAdd: [ + { + ...paramsV1_5_1AddEntry(), + outboundRateLimiterConfig: { enabled: true, capacity: 1n, rate: 2n }, + }, + ], + }), + ], + // ported from the deleted validate.test.ts: a selector must not be accepted just because it + // fits a wider integer type — uint64 is the tighter bound, and uint128 amounts have a + // ceiling of their own + [ + 'remoteChainSelectorsToRemove[0]', + paramsV1_5_1({ remoteChainSelectorsToRemove: [2n ** 64n] }), + ], + [ + 'chainsToAdd[0].remoteChainSelector', + paramsV1_5_1({ + chainsToAdd: [{ ...paramsV1_5_1AddEntry(), remoteChainSelector: 2n ** 64n }], + }), + ], + [ + 'chainsToAdd[0].inboundRateLimiterConfig.capacity', + paramsV1_5_1({ + chainsToAdd: [ + { + ...paramsV1_5_1AddEntry(), + inboundRateLimiterConfig: { enabled: true, capacity: 2n ** 128n, rate: 1n }, + }, + ], + }), + ], + // an enabled config defaults nothing, so an omitted amount is blamed by the bound check + [ + 'chainsToAdd[0].inboundRateLimiterConfig.rate', + paramsV1_5_1({ + chainsToAdd: [ + { + ...paramsV1_5_1AddEntry(), + inboundRateLimiterConfig: { enabled: true, capacity: 1n }, + }, + ], + }), + ], + // a disabled config must be all-zero, and the whole direction is blamed, not one amount + [ + 'chainsToAdd[0].outboundRateLimiterConfig', + paramsV1_5_1({ + chainsToAdd: [ + { + ...paramsV1_5_1AddEntry(), + outboundRateLimiterConfig: { enabled: false, capacity: 1n }, + }, + ], + }), + ], + ['chains', paramsV1_5_0({ chains: [] })], + ['chains[0]', paramsV1_5_0({ chains: ['nope'] })], + [ + 'chains[0].remoteChainSelector', + paramsV1_5_0({ chains: [{ ...paramsV1_5_0Entry(), remoteChainSelector: 1 }] }), + ], + ['chains[0].allowed', paramsV1_5_0({ chains: [{ ...paramsV1_5_0Entry(), allowed: 'yes' }] })], + [ + 'chains[0].remotePoolAddress', + paramsV1_5_0({ chains: [{ ...paramsV1_5_0Entry(), remotePoolAddress: '0x' }] }), + ], + ] + + for (const [param, params] of cases) { + it(`rejects an invalid ${param} before any RPC`, async () => { + const { chain, probes } = stubChain() + await assert.rejects( + () => op.generate(chain, params), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyChainUpdates' && + err.context.param === param, + ) + assert.equal(probes(), 0, 'no RPC should be issued for an invalid param') + }) + } + }) + + /** + * Three guards that each exist because the *un*guarded outcome is worse than a local failure: + * + * - A **hole** survives element validation outright — `.forEach`/`.map` skip holes — so it used + * to reach ethers as `undefined` and surface as a bare `TypeError` with no + * `operation`/`param` context, and only after the `typeAndVersion` probe had been spent. + * - A **`0n` selector** on an added lane is not guarded on-chain: `s_remoteChainSelectors.add(0)` + * succeeds, so the transaction *mines as a success* and leaves a permanently unroutable lane in + * `getSupportedChains()` that a second owner transaction has to remove. + * - A **duplicate** reverts cleanly on-chain, so this one only saves a transaction — but the + * sibling ops (`setChainRateLimiterConfigs`, and `remotePoolAddresses` within a lane) already + * reject it, and consistency across the family is worth more than the one saved revert. + * + * Every rejection asserts `probes() === 0`: a guard that fires *after* the version probe has + * already broken the "fail before RPC" promise, so the counter is the real subject here. + */ + describe('array density, junk selectors and duplicates', () => { + /** `[first, , last]` — length 3, index 1 absent, which every array method skips. */ + function sparse(first: T, last: T): T[] { + const array = [first] + array[2] = last + return array + } + + /** A v1.5.0 lane whose rate limits are both disabled, so `allowed: false` stays legal. */ + const lane = (remoteChainSelector: bigint, allowed: boolean) => ({ + ...paramsV1_5_0Entry(), + remoteChainSelector, + allowed, + inboundRateLimiterConfig: OUTBOUND, + outboundRateLimiterConfig: OUTBOUND, + }) + const add = (remoteChainSelector: bigint) => ({ + ...paramsV1_5_1AddEntry(), + remoteChainSelector, + }) + + const cases: [string, string, ApplyChainUpdatesParams][] = [ + [ + 'a hole in chainsToAdd', + 'chainsToAdd[1]', + paramsV1_5_1({ chainsToAdd: sparse(add(SEL_A), add(SEL_B)) }), + ], + [ + 'a hole in remoteChainSelectorsToRemove', + 'remoteChainSelectorsToRemove[1]', + paramsV1_5_1({ remoteChainSelectorsToRemove: sparse(SEL_A, SEL_B) }), + ], + [ + "a hole in a lane's remotePoolAddresses", + 'chainsToAdd[0].remotePoolAddresses[1]', + paramsV1_5_1({ + chainsToAdd: [ + { + ...paramsV1_5_1AddEntry(), + remotePoolAddresses: sparse(REMOTE_POOL_1, REMOTE_POOL_2), + }, + ], + }), + ], + [ + 'a hole in the v1.5.0 chains array', + 'chains[1]', + paramsV1_5_0({ chains: sparse(lane(SEL_A, true), lane(SEL_B, true)) }), + ], + [ + 'a 0n selector in chainsToAdd', + 'chainsToAdd[0].remoteChainSelector', + paramsV1_5_1({ chainsToAdd: [add(0n)] }), + ], + [ + 'a 0n selector on a v1.5.0 lane being added', + 'chains[0].remoteChainSelector', + paramsV1_5_0({ chains: [lane(0n, true)] }), + ], + [ + 'a repeated selector in chainsToAdd', + 'chainsToAdd[1].remoteChainSelector', + paramsV1_5_1({ chainsToAdd: [add(SEL_A), add(SEL_A)] }), + ], + [ + 'a repeated selector in remoteChainSelectorsToRemove', + 'remoteChainSelectorsToRemove[1]', + paramsV1_5_1({ remoteChainSelectorsToRemove: [SEL_A, SEL_A] }), + ], + [ + 'a repeated selector in the v1.5.0 chains array', + 'chains[1].remoteChainSelector', + paramsV1_5_0({ chains: [lane(SEL_A, true), lane(SEL_A, false)] }), + ], + ] + + for (const [name, param, params] of cases) { + it(`rejects ${name} before any RPC`, async () => { + const { chain, probes } = stubChain() + await assert.rejects( + () => op.generate(chain, params), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyChainUpdates' && + err.context.param === param, + ) + assert.equal(probes(), 0, `${name} must fail before the typeAndVersion probe`) + }) + } + + // The over-rejection side. Each of these is a legitimate call that the guards above must not + // swallow, and each is the *only* way to express its intent. + it('accepts a 0n selector in remoteChainSelectorsToRemove, so a polluted pool can be repaired', async () => { + const { chain } = stubChain() + const unsigned = await op.generate( + chain, + paramsV1_5_1({ remoteChainSelectorsToRemove: [0n], chainsToAdd: [] }), + ) + const [removals, adds] = FRESH_V1_5_1.decodeFunctionData( + 'applyChainUpdates', + unsigned.transactions[0]!.data!, + ) + assert.deepEqual([...(removals as bigint[])], [0n]) + assert.equal((adds as unknown[]).length, 0) + }) + + it('accepts a v1.5.0 removal of a 0n lane, where allowed: false is the removal', async () => { + const { chain } = stubChain(TokenPoolVersion.V1_5_0) + const unsigned = await op.generate(chain, paramsV1_5_0({ chains: [lane(0n, false)] })) + const [chains] = FRESH_V1_5_0.decodeFunctionData( + 'applyChainUpdates', + unsigned.transactions[0]!.data!, + ) + const [entry] = chains as [{ remoteChainSelector: bigint; allowed: boolean }] + assert.equal(entry.remoteChainSelector, 0n) + assert.equal(entry.allowed, false) + }) + + it('keeps the wholesale-replace idiom: one selector in both arrays at once', async () => { + const { chain } = stubChain() + const unsigned = await op.generate( + chain, + paramsV1_5_1({ remoteChainSelectorsToRemove: [SEL_A], chainsToAdd: [add(SEL_A)] }), + ) + const [removals, adds] = FRESH_V1_5_1.decodeFunctionData( + 'applyChainUpdates', + unsigned.transactions[0]!.data!, + ) + assert.deepEqual([...(removals as bigint[])], [SEL_A]) + const [entry] = adds as [{ remoteChainSelector: bigint }] + assert.equal(entry.remoteChainSelector, SEL_A) + }) + + it('rejects the zero pool address before any RPC', async () => { + const { chain, probes } = stubChain() + await assert.rejects( + () => op.generate(chain, paramsV1_5_1({ poolAddress: ZeroAddress })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyChainUpdates' && + err.context.param === 'poolAddress', + ) + assert.equal(probes(), 0) + }) + }) + + describe('execute', () => { + it('signs and submits, resolving to the tx hash', async () => { + assert.deepEqual( + await op.execute(stubChain().chain, { + ...paramsV1_5_1({ sender: undefined }), + wallet: fakeSigner(), + }), + { hash: HASH }, + ) + }) + + it('maps an on-chain revert to CCIPExecTxRevertedError', async () => { + await assert.rejects( + () => + op.execute(stubChain().chain, { + ...paramsV1_5_1({ sender: undefined }), + wallet: fakeSigner(makeError('execution reverted', 'CALL_EXCEPTION')), + }), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && err.context.operation === 'applyChainUpdates', + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => op.execute(stubChain().chain, { ...paramsV1_5_1(), wallet: {} }), + CCIPWalletInvalidError, + ) + }) + + it('rejects a sender that is not the executing wallet', async () => { + await assert.rejects( + () => + op.execute(stubChain().chain, { + ...paramsV1_5_1({ sender: NOT_OWNER }), + wallet: fakeSigner(), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyChainUpdates' && + err.context.param === 'sender', + ) + }) + + it('rejects a wallet that is not the pool owner', async () => { + await assert.rejects( + () => + op.execute(stubChain(TokenPoolVersion.V1_5_1, 'BurnMint', NOT_OWNER).chain, { + ...paramsV1_5_1({ sender: undefined }), + wallet: fakeSigner(), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyChainUpdates' && + err.context.param === 'sender', + ) + }) + }) + + /** + * v1.5.0's `applyChainUpdates` validates BOTH directions with + * `RateLimiter._validateTokenBucketConfig(config, mustBeDisabled: !update.allowed)`, which + * reverts `RateLimitMustBeDisabled()` when `isEnabled && mustBeDisabled`. A removal carrying a + * lane's current (enabled) limits — the obvious way to write one, by reading the lane back and + * flipping `allowed` — therefore always reverts, so it has to fail locally instead. + * + * v1.5.1+ has no such rule: removals there are a separate `remoteChainSelectorsToRemove` array + * and the shape has no `allowed` bit at all, so there is nothing to apply it to. + */ + describe('v1.5.0 lane removal requires both rate limits disabled', () => { + const removal = (overrides: Record) => + paramsV1_5_0({ + chains: [{ ...paramsV1_5_0Entry(), allowed: false, ...overrides }], + }) + + for (const direction of ['inboundRateLimiterConfig', 'outboundRateLimiterConfig'] as const) { + it(`rejects allowed: false with an enabled ${direction}, before any RPC`, async () => { + const { chain, probes } = stubChain(TokenPoolVersion.V1_5_0) + await assert.rejects( + () => + op.generate( + chain, + removal({ + inboundRateLimiterConfig: { enabled: false }, + outboundRateLimiterConfig: { enabled: false }, + [direction]: { enabled: true, capacity: 100_000n, rate: 167n }, + }), + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyChainUpdates' && + err.context.param === `chains[0].${direction}`, + ) + assert.equal(probes(), 0, 'the rule needs no version, so it must fail before any RPC') + }) + } + + it('accepts allowed: false when both directions are disabled', async () => { + const { chain } = stubChain(TokenPoolVersion.V1_5_0) + const unsigned = await op.generate( + chain, + removal({ + inboundRateLimiterConfig: { enabled: false }, + outboundRateLimiterConfig: { enabled: false }, + }), + ) + assert.equal(unsigned.transactions[0]!.data!.slice(0, 10), '0xdb6327dc') + }) + + it('does not constrain enabled limits when allowed is true', async () => { + const { chain } = stubChain(TokenPoolVersion.V1_5_0) + const unsigned = await op.generate( + chain, + paramsV1_5_0({ chains: [{ ...paramsV1_5_0Entry(), allowed: true }] }), + ) + assert.equal(unsigned.transactions[0]!.data!.slice(0, 10), '0xdb6327dc') + }) + }) + + /** + * The enabled-bucket rate bound is version-dependent, so it is applied in the encoder (the first + * place the pool version is known) rather than in `validate()`: + * + * - v1.5.0/v1.5.1 revert `InvalidRateLimitRate` unless `0 < rate < capacity`. + * - v1.6.1/v2.0.0 only revert on `rate > capacity`, so `rate === capacity` and `rate === 0n` are + * legitimate — the accept-side cases below exist so nobody tightens the rule globally. + */ + describe('version-specific rate-limit bounds', () => { + const STRICT_CASES = [ + { label: 'rate === capacity', limit: { enabled: true, capacity: 10n, rate: 10n } }, + { label: 'a zero rate', limit: { enabled: true, capacity: 10n, rate: 0n } }, + ] as const + + for (const { label, limit } of STRICT_CASES) { + it(`rejects ${label} on a v1.5.0 pool`, async () => { + await assert.rejects( + () => + op.generate( + stubChain(TokenPoolVersion.V1_5_0).chain, + paramsV1_5_0({ + chains: [{ ...paramsV1_5_0Entry(), inboundRateLimiterConfig: limit }], + }), + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyChainUpdates' && + err.context.param === 'chains[0].inboundRateLimiterConfig.rate', + ) + }) + + it(`rejects ${label} on a v1.5.1 pool`, async () => { + await assert.rejects( + () => + op.generate( + stubChain(TokenPoolVersion.V1_5_1).chain, + paramsV1_5_1({ + chainsToAdd: [{ ...paramsV1_5_1AddEntry(), inboundRateLimiterConfig: limit }], + }), + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyChainUpdates' && + err.context.param === 'chainsToAdd[0].inboundRateLimiterConfig.rate', + ) + }) + + for (const version of [TokenPoolVersion.V1_6_1, TokenPoolVersion.V2_0_0] as const) { + it(`accepts ${label} on a v${version} pool`, async () => { + const unsigned = await op.generate( + stubChain(version).chain, + paramsV1_5_1({ + remoteChainSelectorsToRemove: [], + chainsToAdd: [{ ...paramsV1_5_1AddEntry(), inboundRateLimiterConfig: limit }], + }), + ) + assert.equal( + unsigned.transactions[0]!.data, + FRESH_V1_5_1.encodeFunctionData('applyChainUpdates', [ + [], + [ + { + remoteChainSelector: SEL_A, + remotePoolAddresses: [REMOTE_POOL_1], + remoteTokenAddress: REMOTE_TOKEN, + outboundRateLimiterConfig: ABI_OUTBOUND, + inboundRateLimiterConfig: { + isEnabled: true, + capacity: limit.capacity, + rate: limit.rate, + }, + }, + ], + ]), + ) + }) + } + } + + it('still rejects rate > capacity on a v2.0.0 pool', async () => { + await assert.rejects( + () => + op.generate( + stubChain(TokenPoolVersion.V2_0_0).chain, + paramsV1_5_1({ + chainsToAdd: [ + { + ...paramsV1_5_1AddEntry(), + inboundRateLimiterConfig: { enabled: true, capacity: 10n, rate: 11n }, + }, + ], + }), + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'chainsToAdd[0].inboundRateLimiterConfig.rate', + ) + }) + }) + + describe('version dispatch', () => { + for (const { version, params, data, otherParams } of DISPATCH) { + const shape = version === TokenPoolVersion.V1_5_0 ? 'chains[]' : 'add/remove' + + it(`picks the ${shape} encoder for a v${version} pool`, async () => { + const unsigned = await op.generate(stubChain(version).chain, params()) + assert.equal(unsigned.transactions[0]!.data, data) + // the two signatures have different selectors, so this pins the encoder, not just the args + assert.equal( + unsigned.transactions[0]!.data.slice(0, 10), + version === TokenPoolVersion.V1_5_0 ? '0xdb6327dc' : '0xe8a1da17', + ) + }) + + it(`rejects the wrong declared version for a v${version} pool`, async () => { + await assert.rejects( + () => op.generate(stubChain(version).chain, otherParams()), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyChainUpdates' && + err.context.param === 'version', + ) + }) + } + }) +}) + +/** One valid v1.5.1 addition, to spread invalid fields over. */ +function paramsV1_5_1AddEntry() { + return { + remoteChainSelector: SEL_A, + remoteTokenAddress: REMOTE_TOKEN, + remotePoolAddresses: [REMOTE_POOL_1], + inboundRateLimiterConfig: INBOUND, + outboundRateLimiterConfig: OUTBOUND, + } +} + +/** One valid v1.5.0 lane update, to spread invalid fields over. */ +function paramsV1_5_0Entry() { + return { + remoteChainSelector: SEL_A, + allowed: true, + remoteTokenAddress: REMOTE_TOKEN, + remotePoolAddress: REMOTE_POOL_1, + inboundRateLimiterConfig: INBOUND, + outboundRateLimiterConfig: OUTBOUND, + } +} diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/apply-chain-updates.ts b/ccip-sdk/src/cct/evm/token-pool/operations/apply-chain-updates.ts new file mode 100644 index 000000000..0e16d5a9d --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/apply-chain-updates.ts @@ -0,0 +1,516 @@ +/** + * applyChainUpdates — configures, enables and disables a token pool's remote lanes: the remote + * token, the remote pool(s) allowed to bridge into it, and both directional rate limits. + * + * The one CCT pool write whose *parameters* changed shape mid-life, so it is discriminated on + * {@link ApplyChainUpdatesParams.version} rather than version-transparent, and sectioned by version + * so each shape's type, parser and encoder sit together. + * + * @packageDocumentation + */ + +import type { Interface } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { + parseHexBytes, + parseRecord, + parseUniqueHexBytesArray, + validateArray, + validateBoolean, + validateNonZeroAddress, + validateUint64, +} from '../../validate.ts' +import { + TokenPoolVersion, + assertPoolOwner, + getTokenPoolInterface, + resolveEncoder, + resolveTokenPool, +} from '../contracts.ts' +import { + type ParsedRateLimitConfig, + type RateLimitConfig, + parseRateLimitConfig, +} from '../rate-limit.ts' + +// --------------------------------------------------------------------------- +// Shared +// --------------------------------------------------------------------------- + +/** + * The `version` discriminant of {@link ApplyChainUpdatesParams}: the two parameter shapes + * `applyChainUpdates` has had, each spelled as the version that introduced it — so `1.5.1` is the + * shape for every pool from v1.5.1 up, v1.6.1 and v2.0.0 included. + */ +export type ApplyChainUpdatesParamVersion = + | typeof TokenPoolVersion.V1_5_0 + | typeof TokenPoolVersion.V1_5_1 + +/** The lane fields both parameter shapes share, and which encode identically. */ +type ChainUpdateCommon = { + /** CCIP selector of the remote chain (`uint64`). */ + remoteChainSelector: bigint + /** Hex-encoded remote token address, `0x` prefix optional; must be non-empty whole bytes. */ + remoteTokenAddress: string + /** Rate limit for tokens received from the remote chain. */ + inboundRateLimiterConfig: RateLimitConfig + /** Rate limit for tokens sent to the remote chain. */ + outboundRateLimiterConfig: RateLimitConfig +} + +/** The top-level parameters both shapes share; each version adds its own lane arrays. */ +type ApplyChainUpdatesBaseParams = { + /** Token pool whose lanes are being configured. */ + poolAddress: string + /** + * Pool owner; sets `tx.from` for offline / multisig signing. When supplied it is also + * pre-flighted against the pool's on-chain `owner()`, so an unauthorized caller fails here + * rather than as an opaque revert. + */ + sender?: string +} + +/** A lane with its rate limits resolved — derived, so the parsed and public shapes cannot drift. */ +type WithParsedRateLimits = Omit & { + inboundRateLimiterConfig: ParsedRateLimitConfig + outboundRateLimiterConfig: ParsedRateLimitConfig +} + +/** + * Parses a lane's `remoteChainSelector`: a `uint64`, unique within its own array, and — for a lane + * being *added* — non-zero. `seen` is mutated as each selector is accepted, and is per-array: the + * same selector in both v1.5.1 arrays is the replace idiom. + * + * @remarks `requireNonZero` holds only for an addition, which `TokenPool.applyChainUpdates` does + * not guard: `s_remoteChainSelectors.add(0)` succeeds, so the tx **mines as a success** and leaves + * `getSupportedChains()` holding a lane nothing can route. A removal is how such a pool is + * repaired, so `0n` stays legal there. Not a *known*-selector check, though — the registry lags new + * chains, and rejecting a real-but-unrecognised selector is the worse failure. + */ +function parseLaneSelector( + operation: string, + param: string, + selector: unknown, + seen: Set, + requireNonZero: boolean, +): bigint { + validateUint64(operation, param, selector) + if (requireNonZero && selector === 0n) { + throw new CCTParamsInvalidError( + operation, + param, + 'must not be zero: 0 is not a CCIP chain selector, and the pool would accept it as a permanently unroutable lane rather than reverting', + ) + } + if (seen.has(selector)) { + throw new CCTParamsInvalidError( + operation, + param, + `is a duplicate of an earlier entry in the same array (${selector}); each lane may appear only once`, + ) + } + seen.add(selector) + return selector +} + +/** Parses the lane fields both shapes share. */ +function parseLaneCommon( + operation: string, + path: string, + update: { [k: string]: unknown }, + seen: Set, + requireNonZero: boolean, +): WithParsedRateLimits { + return { + remoteChainSelector: parseLaneSelector( + operation, + `${path}.remoteChainSelector`, + update.remoteChainSelector, + seen, + requireNonZero, + ), + remoteTokenAddress: parseHexBytes( + operation, + `${path}.remoteTokenAddress`, + update.remoteTokenAddress, + ), + inboundRateLimiterConfig: parseRateLimitConfig( + operation, + `${path}.inboundRateLimiterConfig`, + update.inboundRateLimiterConfig, + null, + ), + outboundRateLimiterConfig: parseRateLimitConfig( + operation, + `${path}.outboundRateLimiterConfig`, + update.outboundRateLimiterConfig, + null, + ), + } +} + +// --------------------------------------------------------------------------- +// v1.5.0 +// --------------------------------------------------------------------------- + +/** + * One lane's configuration on a **v1.5.0** pool. + * @remarks Field-for-field the Solana `ChainUpdate` in + * `cct/solana/token-pool/operations/apply-chain-updates.ts`, minus its Solana-only + * `remoteTokenDecimals`. + */ +export type ChainUpdateV1_5_0 = ChainUpdateCommon & { + /** + * Whether the lane is enabled. **v1.5.0 only** — `false` removes the lane, which is how this + * version spells v1.5.1+'s `remoteChainSelectorsToRemove`. Every other field is still required + * and still encoded for a removal, and both rate limits must be `{ enabled: false }`: v1.5.0 + * validates them with `mustBeDisabled = !update.allowed` and reverts `RateLimitMustBeDisabled()` + * otherwise, so passing a lane's current (enabled) limits back through is rejected. + */ + allowed: boolean + /** Hex-encoded remote pool address, `0x` prefix optional. Singular at v1.5.0 — one pool per lane. */ + remotePoolAddress: string +} + +/** {@link ApplyChainUpdatesParamsV1_5_0} once parsed — derived, so the two cannot drift. */ +type ParsedApplyChainUpdatesParamsV1_5_0 = Omit & { + chains: WithParsedRateLimits[] +} + +/** + * Parses the v1.5.0 `chains` array. See {@link ChainUpdateV1_5_0.allowed} for why a removal must + * also carry both rate limits disabled. + */ +function parseChainsV1_5_0(operation: string, chains: unknown) { + validateArray(operation, 'chains', chains, 1) + const seen = new Set() + return chains.map((entry, i) => { + const path = `chains[${i}]` + const update = parseRecord(operation, path, entry, 'chain update') + const { allowed } = update + validateBoolean(operation, `${path}.allowed`, allowed) + const lane = { + ...parseLaneCommon(operation, path, update, seen, allowed), + allowed, + remotePoolAddress: parseHexBytes( + operation, + `${path}.remotePoolAddress`, + update.remotePoolAddress, + ), + } + const stillEnabled = + !allowed && + (['inboundRateLimiterConfig', 'outboundRateLimiterConfig'] as const).find( + (direction) => lane[direction].enabled, + ) + if (stillEnabled) { + throw new CCTParamsInvalidError( + operation, + `${path}.${stillEnabled}`, + 'must be disabled when allowed is false: v1.5.0 validates both rate limits with mustBeDisabled = !allowed and reverts RateLimitMustBeDisabled — pass { enabled: false } for a removal', + ) + } + return lane + }) +} + +/** Encodes the v1.5.0 signature. */ +const encodeV1_5_0 = ( + iface: Interface, + params: ParsedApplyChainUpdatesParamsV1_5_0, +): UnsignedEVMTx => + callTx( + params.poolAddress, + iface.encodeFunctionData('applyChainUpdates', [ + params.chains.map((lane) => ({ + ...lane, + // re-key the shared `enabled` to the ABI's `isEnabled` + inboundRateLimiterConfig: (({ enabled: isEnabled, capacity, rate }) => ({ + isEnabled, + capacity, + rate, + }))(lane.inboundRateLimiterConfig), + outboundRateLimiterConfig: (({ enabled: isEnabled, capacity, rate }) => ({ + isEnabled, + capacity, + rate, + }))(lane.outboundRateLimiterConfig), + })), + ]), + ) + +// --------------------------------------------------------------------------- +// v1.5.1+ +// --------------------------------------------------------------------------- + +/** + * One lane's configuration on a **v1.5.1+** pool. No `allowed` bit: removals are a separate array + * on {@link ApplyChainUpdatesParams}. + */ +export type ChainUpdateV1_5_1 = ChainUpdateCommon & { + /** + * Hex-encoded remote pool addresses, `0x` prefix optional — plural, because a lane may accept + * several remote pools, e.g. while migrating one. Non-empty, and unique within the lane + * (compared as bytes, so `0xAB` and `ab` collide). + */ + remotePoolAddresses: string[] +} + +/** {@link ApplyChainUpdatesParamsV1_5_1} once parsed — derived, so the two cannot drift. */ +type ParsedApplyChainUpdatesParamsV1_5_1 = Omit & { + chainsToAdd: WithParsedRateLimits[] +} + +/** Parses the v1.5.1+ pair of arrays: removals (applied first on-chain), then additions. */ +function parseChainsV1_5_1( + operation: string, + chainsToAdd: unknown, + remoteChainSelectorsToRemove: unknown, +) { + validateArray(operation, 'chainsToAdd', chainsToAdd) + validateArray(operation, 'remoteChainSelectorsToRemove', remoteChainSelectorsToRemove) + if (!chainsToAdd.length && !remoteChainSelectorsToRemove.length) { + throw new CCTParamsInvalidError( + operation, + 'chainsToAdd', + 'at least one of chainsToAdd or remoteChainSelectorsToRemove must be non-empty', + ) + } + + const seenRemovals = new Set() + const removals = remoteChainSelectorsToRemove.map((selector, i) => + parseLaneSelector( + operation, + `remoteChainSelectorsToRemove[${i}]`, + selector, + seenRemovals, + false, + ), + ) + + const seenAdds = new Set() + const adds = chainsToAdd.map((entry, i) => { + const path = `chainsToAdd[${i}]` + const update = parseRecord(operation, path, entry, 'chain update') + return { + ...parseLaneCommon(operation, path, update, seenAdds, true), + remotePoolAddresses: parseUniqueHexBytesArray( + operation, + `${path}.remotePoolAddresses`, + update.remotePoolAddresses, + ), + } + }) + return { chainsToAdd: adds, remoteChainSelectorsToRemove: removals } +} + +/** Encodes the v1.5.1+ signature. */ +const encodeV1_5_1 = ( + iface: Interface, + params: ParsedApplyChainUpdatesParamsV1_5_1, +): UnsignedEVMTx => + callTx( + params.poolAddress, + iface.encodeFunctionData('applyChainUpdates', [ + params.remoteChainSelectorsToRemove, + params.chainsToAdd.map((lane) => ({ + ...lane, + // re-key the shared `enabled` to the ABI's `isEnabled` + inboundRateLimiterConfig: (({ enabled: isEnabled, capacity, rate }) => ({ + isEnabled, + capacity, + rate, + }))(lane.inboundRateLimiterConfig), + outboundRateLimiterConfig: (({ enabled: isEnabled, capacity, rate }) => ({ + isEnabled, + capacity, + rate, + }))(lane.outboundRateLimiterConfig), + })), + ]), + ) + +/** + * Parameters for {@link ApplyChainUpdates}, discriminated on `version` — the calldata shape you are + * writing, not a free-form pool version; see {@link ApplyChainUpdatesParamVersion}. + * + * The two signatures have different selectors (`0xdb6327dc` vs `0xe8a1da17`), so + * {@link ApplyChainUpdates.buildUnsigned} checks the declaration against the pool's own + * `typeAndVersion`: a mismatch is a parameter error rather than a tx that reverts on an unknown + * function. + */ +export type ApplyChainUpdatesParams = ApplyChainUpdatesParamsV1_5_0 | ApplyChainUpdatesParamsV1_5_1 + +/** The **v1.5.0** parameter shape: a single `chains` array, each lane carrying its `allowed` bit. */ +export type ApplyChainUpdatesParamsV1_5_0 = ApplyChainUpdatesBaseParams & { + version: typeof TokenPoolVersion.V1_5_0 + /** + * Lanes to configure; `allowed: false` removes one. At least one entry, no holes, and a given + * `remoteChainSelector` may appear only once. + */ + chains: ChainUpdateV1_5_0[] +} + +/** The **v1.5.1+** parameter shape: additions and removals as two arrays. */ +export type ApplyChainUpdatesParamsV1_5_1 = ApplyChainUpdatesBaseParams & { + version: typeof TokenPoolVersion.V1_5_1 + /** + * Lanes to add or reconfigure. To replace a lane's remote pools wholesale, list its selector + * here *and* in `remoteChainSelectorsToRemove` — the contract applies removals first, so that + * cross-array pairing stays legal. Within this array a selector may appear only once, and may + * not be `0n`; holes are rejected too. + */ + chainsToAdd: ChainUpdateV1_5_1[] + /** + * Lanes to remove, applied before `chainsToAdd`. No duplicates and no holes; `0n` *is* accepted + * here, so a pool already holding a junk lane can be cleaned up. + */ + remoteChainSelectorsToRemove: bigint[] +} + +/** + * {@link ApplyChainUpdatesParams} as {@link ApplyChainUpdates.parse} leaves it. The encoders add + * no validation of their own — a parsed lane is already a `ChainUpdate` struct. + */ +type ParsedApplyChainUpdatesParams = + | ParsedApplyChainUpdatesParamsV1_5_0 + | ParsedApplyChainUpdatesParamsV1_5_1 + +/** Encodes parsed params into `applyChainUpdates` calldata, widened over the parsed union. */ +type EncodeFn = (iface: Interface, params: ParsedApplyChainUpdatesParams) => UnsignedEVMTx + +/** One {@link ApplyChainUpdates.encoders} entry: the shape it accepts, and the {@link EncodeFn} for it. */ +type Encoder = { + shape: V + encode: EncodeFn +} + +/** + * Configures, enables and disables a token pool's remote lanes via `applyChainUpdates`. + * + * @remarks Owner-gated on-chain (`onlyOwner`). Supply `sender` to have that checked against the + * pool's `owner()` before a tx is built; {@link ApplyChainUpdates.execute} defaults it to the + * signing wallet, the only address a broadcast tx can satisfy it with. + */ +export class ApplyChainUpdates extends EVMOperation< + ApplyChainUpdatesParams, + ParsedApplyChainUpdatesParams +> { + readonly name = 'applyChainUpdates' + + /** Encoder per pool version, floor-matched; v1.6.1 and v2.0.0 inherit v1.5.1's. */ + private readonly encoders = { + [TokenPoolVersion.V1_5_0]: { + shape: TokenPoolVersion.V1_5_0, + encode: encodeV1_5_0, + }, + [TokenPoolVersion.V1_5_1]: { + shape: TokenPoolVersion.V1_5_1, + encode: encodeV1_5_1, + }, + } as { [V in ApplyChainUpdatesParamVersion]?: Encoder } + + /** + * Validates the pool address and every lane entry before any RPC, *keeping* what each check + * produced so neither {@link buildUnsigned} nor an encoder re-derives it. Only the + * version-conditional rate bound is left to {@link assertRateBounds}. + * @throws {@link CCTParamsInvalidError} if `version` is unknown, or any lane field is invalid + */ + protected override parse(params: ApplyChainUpdatesParams): ParsedApplyChainUpdatesParams { + validateNonZeroAddress(this.name, 'poolAddress', params.poolAddress) + const version: string = params.version + switch (params.version) { + case TokenPoolVersion.V1_5_0: + return { + ...params, + chains: parseChainsV1_5_0(this.name, params.chains), + } + case TokenPoolVersion.V1_5_1: + return { + ...params, + ...parseChainsV1_5_1(this.name, params.chainsToAdd, params.remoteChainSelectorsToRemove), + } + default: + throw new CCTParamsInvalidError( + this.name, + 'version', + `must be one of ${TokenPoolVersion.V1_5_0}, ${ + TokenPoolVersion.V1_5_1 + }, got ${String(version)}`, + ) + } + } + + /** + * Applies the version-conditional rate bound, which needs the version `resolveTokenPool` has + * just reported, via the shared {@link parseRateLimitConfig}. + */ + private assertRateBounds(params: ParsedApplyChainUpdatesParams, version: TokenPoolVersion): void { + const lanes = + params.version === TokenPoolVersion.V1_5_0 + ? params.chains.map((lane, i) => [`chains[${i}]`, lane] as const) + : params.chainsToAdd.map((lane, i) => [`chainsToAdd[${i}]`, lane] as const) + + for (const [path, lane] of lanes) { + for (const direction of ['inboundRateLimiterConfig', 'outboundRateLimiterConfig'] as const) { + // already parsed to the shared `enabled` shape; re-running with the resolved version + // applies the version-conditional bound + parseRateLimitConfig(this.name, `${path}.${direction}`, lane[direction], version) + } + } + } + + /** + * Resolves the pool's type and version, applies the checks that needed it, then encodes. + * @throws {@link CCTParamsInvalidError} if the declared `version` is not this pool's shape, a + * rate limit breaks its enabled-bucket bound, or `sender` is not the pool owner + * @throws {@link CCTContractTypeInvalidError} if the address is not a supported pool type + * @throws {@link CCTContractVersionUnsupportedError} if the pool reports an unknown version + */ + protected async buildUnsigned( + chain: EVMChain, + params: ParsedApplyChainUpdatesParams, + ): Promise { + const { type, version } = await resolveTokenPool(chain, params.poolAddress) + + // explicit type argument: inference would otherwise fix `F` to the first entry's `shape` + const { shape, encode } = resolveEncoder>( + this.encoders, + version, + this.name, + ) + if (params.version !== shape) + throw new CCTParamsInvalidError( + this.name, + 'version', + `must be '${shape}' for this pool, which reports v${version} — the two signatures have different selectors, so the declared shape would not exist on-chain`, + ) + + this.assertRateBounds(params, version) + + if (params.sender !== undefined) + await assertPoolOwner(this.name, chain, params.poolAddress, params.sender) + + return encode(getTokenPoolInterface(type, version), params) + } + + /** + * Signs and submits as the pool owner, defaulting `sender` to the signing wallet — the only + * address the contract's `onlyOwner` check can pass. See + * {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is rejected. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/deploy-token-pool.test.ts b/ccip-sdk/src/cct/evm/token-pool/operations/deploy-token-pool.test.ts new file mode 100644 index 000000000..fcd04780f --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/deploy-token-pool.test.ts @@ -0,0 +1,301 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { ZeroAddress, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { CCTParamsInvalidError, CCTTxFailedError } from '../../../errors.ts' +import BURN_FROM_MINT_V2_0_0 from '../../artifacts/bytecode/V2_0_0/burn-from-mint-token-pool.ts' +import BURN_MINT_V2_0_0 from '../../artifacts/bytecode/V2_0_0/burn-mint-token-pool.ts' +import BURN_WITH_FROM_MINT_V2_0_0 from '../../artifacts/bytecode/V2_0_0/burn-with-from-mint-token-pool.ts' +import LOCK_RELEASE_V2_0_0 from '../../artifacts/bytecode/V2_0_0/lock-release-token-pool.ts' +import { type DeployTokenPoolParams, DeployTokenPool } from './deploy-token-pool.ts' + +const SENDER = '0x' + '11'.repeat(20) +const TOKEN = '0x' + '22'.repeat(20) +const RMN_PROXY = '0x' + '33'.repeat(20) +const ROUTER = '0x' + '44'.repeat(20) +const HOOKS = '0x' + '55'.repeat(20) +const LOCKBOX = '0x' + '66'.repeat(20) +const DEPLOYED = '0x' + '77'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +const COMMON = { token: TOKEN, localTokenDecimals: 18, rmnProxy: RMN_PROXY, router: ROUTER } + +// Word encodings (32-byte, hex) reused across the golden vectors below. +const W_TOKEN = '0000000000000000000000002222222222222222222222222222222222222222' +const W_DECIMALS = '0000000000000000000000000000000000000000000000000000000000000012' +const W_RMN = '0000000000000000000000003333333333333333333333333333333333333333' +const W_ROUTER = '0000000000000000000000004444444444444444444444444444444444444444' +const W_HOOKS = '0000000000000000000000005555555555555555555555555555555555555555' +const W_LOCKBOX = '0000000000000000000000006666666666666666666666666666666666666666' + +// Golden vectors: pinned 2.0.0 constructor-arg encodings for the fixed inputs above. Independent +// of the SDK encoder — they guard each pool's init-code against drift. The burn-* variants share +// the `BurnMint` constructor (token, decimals, advancedPoolHooks, rmnProxy, router); LockRelease +// adds `lockbox`. +const BURN_MINT_ARGS = W_TOKEN + W_DECIMALS + W_HOOKS + W_RMN + W_ROUTER +const LOCK_RELEASE_ARGS = W_TOKEN + W_DECIMALS + W_HOOKS + W_RMN + W_ROUTER + W_LOCKBOX + +const CASES: { + label: string + params: DeployTokenPoolParams + bytecode: string + ctorArgs: string +}[] = [ + { + label: 'BurnMintTokenPool', + params: { ...COMMON, type: 'BurnMintTokenPool', advancedPoolHooks: HOOKS }, + bytecode: BURN_MINT_V2_0_0, + ctorArgs: BURN_MINT_ARGS, + }, + { + label: 'BurnFromMintTokenPool', + params: { ...COMMON, type: 'BurnFromMintTokenPool', advancedPoolHooks: HOOKS }, + bytecode: BURN_FROM_MINT_V2_0_0, + ctorArgs: BURN_MINT_ARGS, + }, + { + label: 'BurnWithFromMintTokenPool', + params: { ...COMMON, type: 'BurnWithFromMintTokenPool', advancedPoolHooks: HOOKS }, + bytecode: BURN_WITH_FROM_MINT_V2_0_0, + ctorArgs: BURN_MINT_ARGS, + }, + { + label: 'LockReleaseTokenPool', + params: { + ...COMMON, + type: 'LockReleaseTokenPool', + advancedPoolHooks: HOOKS, + lockbox: LOCKBOX, + }, + bytecode: LOCK_RELEASE_V2_0_0, + ctorArgs: LOCK_RELEASE_ARGS, + }, +] + +/** Minimal EVMChain stub — deployTokenPool's build path ignores it; execute uses only these. */ +function stubChain(): EVMChain { + return { + provider: {} as never, + logger: { debug() {}, info() {}, warn() {}, error() {} }, + nextNonce: async () => 0, + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +/** Fake ethers Signer whose deployment receipt carries `contractAddress`. */ +function fakeSigner(opts: { contractAddress?: string | null; waitError?: Error }) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(SENDER), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => + opts.waitError + ? Promise.reject(opts.waitError) + : Promise.resolve({ + status: 1, + contractAddress: + opts.contractAddress === undefined ? DEPLOYED : opts.contractAddress, + }), + }), + } +} + +describe('DeployTokenPool (cct/evm token-pool operation)', () => { + describe('generate (golden vectors per deployable type)', () => { + for (const { label, params, bytecode, ctorArgs } of CASES) { + it(`builds ${label} as init-code with no \`to\``, async () => { + const unsigned = await new DeployTokenPool().generate(stubChain(), { + ...params, + sender: SENDER, + }) + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + const tx = unsigned.transactions[0]! + assert.equal(tx.to, undefined, 'deployment tx has no `to`') + assert.equal(tx.from, SENDER) + assert.ok(tx.data!.startsWith(bytecode), 'data starts with creation bytecode') + assert.equal(tx.data, bytecode + ctorArgs) + }) + } + + it('defaults advancedPoolHooks to the zero address when omitted', async () => { + const unsigned = await new DeployTokenPool().generate(stubChain(), { + ...COMMON, + type: 'BurnMintTokenPool', + }) + const zeroHooks = W_TOKEN + W_DECIMALS + '0'.repeat(64) + W_RMN + W_ROUTER + assert.equal(unsigned.transactions[0]!.data, BURN_MINT_V2_0_0 + zeroHooks) + }) + + it('omits `from` when no sender is given', async () => { + const unsigned = await new DeployTokenPool().generate(stubChain(), { + ...COMMON, + type: 'BurnMintTokenPool', + advancedPoolHooks: HOOKS, + }) + assert.equal(unsigned.transactions[0]!.from, undefined) + }) + }) + + describe('validation', () => { + const base: DeployTokenPoolParams = { + ...COMMON, + type: 'BurnMintTokenPool', + advancedPoolHooks: HOOKS, + } + + it('rejects an invalid token address', async () => { + await assert.rejects( + () => new DeployTokenPool().generate(stubChain(), { ...base, token: 'nope' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'deployTokenPool' && + err.context.param === 'token', + ) + }) + + it('rejects an invalid router address', async () => { + await assert.rejects( + () => new DeployTokenPool().generate(stubChain(), { ...base, router: 'nope' }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'router', + ) + }) + + it('rejects decimals outside 0–255', async () => { + await assert.rejects( + () => new DeployTokenPool().generate(stubChain(), { ...base, localTokenDecimals: 256 }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'localTokenDecimals', + ) + }) + + it('rejects an invalid advancedPoolHooks address', async () => { + await assert.rejects( + () => new DeployTokenPool().generate(stubChain(), { ...base, advancedPoolHooks: 'nope' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'advancedPoolHooks', + ) + }) + + it('rejects an invalid sender', async () => { + await assert.rejects( + () => new DeployTokenPool().generate(stubChain(), { ...base, sender: 'nope' }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects a non-deployable pool type', async () => { + await assert.rejects( + () => + new DeployTokenPool().generate(stubChain(), { + ...COMMON, + type: 'BurnToAddressTokenPool', + } as unknown as DeployTokenPoolParams), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'type', + ) + }) + + it('rejects the zero address for a LockRelease lockbox', async () => { + await assert.rejects( + () => + new DeployTokenPool().generate(stubChain(), { + ...COMMON, + type: 'LockReleaseTokenPool', + advancedPoolHooks: HOOKS, + lockbox: ZeroAddress, + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'lockbox', + ) + }) + + it('rejects an invalid lockbox address', async () => { + await assert.rejects( + () => + new DeployTokenPool().generate(stubChain(), { + ...COMMON, + type: 'LockReleaseTokenPool', + advancedPoolHooks: HOOKS, + lockbox: 'nope', + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'lockbox', + ) + }) + // `lockbox` on a burn pool is a compile-time error (the DeployTokenPoolParams union), so + // there's no runtime case to test. + }) + + describe('execute', () => { + const params: DeployTokenPoolParams = { + ...COMMON, + type: 'BurnMintTokenPool', + advancedPoolHooks: HOOKS, + } + + it('deploys and returns the tx hash and deployed address', async () => { + const result = await new DeployTokenPool().execute(stubChain(), { + ...params, + wallet: fakeSigner({ contractAddress: DEPLOYED }), + }) + assert.deepEqual(result, { + hash: HASH, + contractAddress: DEPLOYED, + verification: { + contract: 'BurnMintTokenPool', + encodedConstructorArgs: '0x' + BURN_MINT_ARGS, + }, + }) + }) + + for (const { label, params: caseParams, ctorArgs } of CASES) { + it(`carries the verification handle for ${label}`, async () => { + const result = await new DeployTokenPool().execute(stubChain(), { + ...caseParams, + wallet: fakeSigner({ contractAddress: DEPLOYED }), + }) + assert.equal(result.verification.contract, caseParams.type) + assert.equal(result.verification.encodedConstructorArgs, '0x' + ctorArgs) + }) + } + + it('throws CCTTxFailedError when the receipt carries no contract address', async () => { + await assert.rejects( + () => + new DeployTokenPool().execute(stubChain(), { + ...params, + wallet: fakeSigner({ contractAddress: null }), + }), + (err: unknown) => + err instanceof CCTTxFailedError && + err.context.operation === 'deployTokenPool' && + !err.isTransient, + ) + }) + + it('throws CCIPExecTxRevertedError when the deployment reverts on-chain', async () => { + await assert.rejects( + () => + new DeployTokenPool().execute(stubChain(), { + ...params, + wallet: fakeSigner({ waitError: makeError('execution reverted', 'CALL_EXCEPTION') }), + }), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && err.context.operation === 'deployTokenPool', + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => new DeployTokenPool().execute(stubChain(), { ...params, wallet: {} }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/deploy-token-pool.ts b/ccip-sdk/src/cct/evm/token-pool/operations/deploy-token-pool.ts new file mode 100644 index 000000000..c65582040 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/deploy-token-pool.ts @@ -0,0 +1,126 @@ +/** + * deployTokenPool — deploys a token pool (`type` selects the contract) via raw init-code at + * v2.0.0. The tx has no `to`; `execute` returns the deployed pool address. Mirrors + * `token/operations/deploy-token.ts`. + * + * @packageDocumentation + */ + +import { type Interface, ZeroAddress } from 'ethers' + +import { CCTParamsInvalidError } from '../../../errors.ts' +import { type DeployArtifact, EVMDeployOperation } from '../../operation.ts' +import { validateAddress, validateNonZeroAddress, validateUint8 } from '../../validate.ts' +import { + type DeployableTokenPoolType, + type TokenPoolFamily, + getTokenPoolArtifact, + getTokenPoolFamily, + isDeployableTokenPoolType, +} from '../contracts.ts' + +/** Deployable pool types + their creation bytecode/artifact live in `../contracts.ts`. */ +export type { DeployableTokenPoolType } + +/** Fields shared by every deployable token pool. */ +interface DeployTokenPoolBaseParams { + /** Address of the token the pool manages. */ + token: string + /** The token's `decimals` (uint8). */ + localTokenDecimals: number + /** RMN proxy address. */ + rmnProxy: string + /** CCIP router address. */ + router: string + /** Advanced pool hooks; defaults to the zero address. */ + advancedPoolHooks?: string + /** Deployer address; sets `tx.from` for offline / multisig signing. */ + sender?: string +} + +/** Params for a burn-* mint pool — the burn family shares one constructor shape. */ +export interface DeployBurnMintTokenPoolParams extends DeployTokenPoolBaseParams { + type: Exclude +} + +/** + * Params for a `LockReleaseTokenPool` — the burn constructor plus `lockbox`. + * + * @remarks `lockbox` must be a pre-deployed `ERC20LockBox` for the *same* `token` (the constructor + * calls `lockbox.isTokenSupported(token)`). Sequence: deployToken → deployLockbox → deployTokenPool + * (this) → authorizeLockboxCallers (`addedCallers: [pool]`) → setPool → configure lanes. + */ +export interface DeployLockReleaseTokenPoolParams extends DeployTokenPoolBaseParams { + type: 'LockReleaseTokenPool' + /** Lockbox address; required and must be non-zero — the v2.0.0 constructor reverts on the zero address. */ + lockbox: string +} + +/** + * Parameters for {@link DeployTokenPool}, discriminated on `type`: the burn-* variants share one + * constructor; `LockReleaseTokenPool` additionally requires `lockbox` (a compile-time guarantee). + */ +export type DeployTokenPoolParams = DeployBurnMintTokenPoolParams | DeployLockReleaseTokenPoolParams + +/** Encodes a v2.0.0 pool constructor into init-code args for a given ABI family. */ +type TokenPoolConstructorEncoder = (iface: Interface, p: DeployTokenPoolParams) => string + +/** Burn-* family constructor: `(token, localTokenDecimals, advancedPoolHooks, rmnProxy, router)`. */ +const encodeBurnMintTokenPool: TokenPoolConstructorEncoder = (iface, p) => + iface.encodeDeploy([ + p.token, + p.localTokenDecimals, + p.advancedPoolHooks ?? ZeroAddress, + p.rmnProxy, + p.router, + ]) + +/** LockRelease constructor: the burn-* args plus `lockbox` (only that variant carries it). */ +const encodeLockReleaseTokenPool: TokenPoolConstructorEncoder = (iface, p) => + iface.encodeDeploy([ + p.token, + p.localTokenDecimals, + p.advancedPoolHooks ?? ZeroAddress, + p.rmnProxy, + p.router, + p.type === 'LockReleaseTokenPool' ? p.lockbox : ZeroAddress, + ]) + +/** Deploys a token pool; `execute` resolves to `{ hash, contractAddress, verification }`. */ +export class DeployTokenPool extends EVMDeployOperation { + readonly name = 'deployTokenPool' + + /** Constructor encoder per ABI {@link TokenPoolFamily}; `type` narrows to its family. */ + private readonly encoders: Record = { + BurnMint: encodeBurnMintTokenPool, + LockRelease: encodeLockReleaseTokenPool, + } + + /** Validates the constructor params before building init-code. */ + protected override validate(params: DeployTokenPoolParams): void { + if (!isDeployableTokenPoolType(params.type)) + throw new CCTParamsInvalidError( + this.name, + 'type', + `unsupported pool type ${String(params.type)}`, + ) + validateAddress(this.name, 'token', params.token) + validateUint8(this.name, 'localTokenDecimals', params.localTokenDecimals) + validateAddress(this.name, 'rmnProxy', params.rmnProxy) + validateAddress(this.name, 'router', params.router) + if (params.advancedPoolHooks !== undefined) + validateAddress(this.name, 'advancedPoolHooks', params.advancedPoolHooks) + if (params.type === 'LockReleaseTokenPool') + validateNonZeroAddress(this.name, 'lockbox', params.lockbox) + } + + /** Deploy artifact for the selected pool `type` (v2.0.0): name + ctor interface + bytecode. */ + protected artifact(p: DeployTokenPoolParams): DeployArtifact { + return getTokenPoolArtifact(p.type) + } + + /** ABI-encodes the pool constructor args via the encoder for the type's ABI family. */ + protected encode(iface: Interface, p: DeployTokenPoolParams): string { + return this.encoders[getTokenPoolFamily(p.type)](iface, p) + } +} diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/get-token-pool-remotes.test.ts b/ccip-sdk/src/cct/evm/token-pool/operations/get-token-pool-remotes.test.ts new file mode 100644 index 000000000..1b88fd23b --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/get-token-pool-remotes.test.ts @@ -0,0 +1,121 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import type { TokenPoolRemote } from '../../../../chain.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { GetTokenPoolRemotes } from './get-token-pool-remotes.ts' + +const POOL = '0x' + '11'.repeat(20) +const SELECTOR = 5009297550715157269n + +const REMOTES: Record = { + 'ethereum-mainnet': { + remoteToken: '0x' + '22'.repeat(20), + remotePools: ['0x' + '33'.repeat(20)], + inboundRateLimiterState: { tokens: 25n, capacity: 50n, rate: 5n }, + outboundRateLimiterState: null, + }, +} + +/** Records every `getTokenPoolRemotes` call so tests can assert forwarding and RPC-freeness. */ +function stubChain(calls: Array<[string, bigint | undefined]> = []): EVMChain { + return { + getTokenPoolRemotes: (tokenPool: string, remoteChainSelector?: bigint) => { + calls.push([tokenPool, remoteChainSelector]) + return Promise.resolve(REMOTES) + }, + } as unknown as EVMChain +} + +describe('GetTokenPoolRemotes (cct/evm)', () => { + describe('query', () => { + it('forwards poolAddress and the selector to chain.getTokenPoolRemotes', async () => { + const calls: Array<[string, bigint | undefined]> = [] + const result = await new GetTokenPoolRemotes().query(stubChain(calls), { + poolAddress: POOL, + remoteChainSelector: SELECTOR, + }) + + assert.equal(result, REMOTES) + assert.deepEqual(calls, [[POOL, SELECTOR]]) + }) + + it('omits the selector to scan every configured lane', async () => { + const calls: Array<[string, bigint | undefined]> = [] + const result = await new GetTokenPoolRemotes().query(stubChain(calls), { poolAddress: POOL }) + + assert.equal(result, REMOTES) + assert.deepEqual(calls, [[POOL, undefined]], 'no selector is forwarded as undefined') + }) + + it('passes the pool address through unnormalised, leaving resolution to the chain reader', async () => { + // the op is a thin delegate: it must not checksum, resolve, or otherwise rewrite the address + const calls: Array<[string, bigint | undefined]> = [] + const lowercase = POOL.toLowerCase() + await new GetTokenPoolRemotes().query(stubChain(calls), { poolAddress: lowercase }) + assert.equal(calls[0]![0], lowercase) + }) + + it('accepts the uint64 selector bounds', async () => { + const calls: Array<[string, bigint | undefined]> = [] + const chain = stubChain(calls) + for (const selector of [0n, 2n ** 64n - 1n]) { + await new GetTokenPoolRemotes().query(chain, { + poolAddress: POOL, + remoteChainSelector: selector, + }) + } + assert.deepEqual( + calls.map(([, selector]) => selector), + [0n, 2n ** 64n - 1n], + ) + }) + }) + + describe('validation', () => { + it('rejects an invalid poolAddress before any RPC', async () => { + let called = false + const chain = { + getTokenPoolRemotes: () => { + called = true + return Promise.resolve(REMOTES) + }, + } as unknown as EVMChain + + await assert.rejects( + () => new GetTokenPoolRemotes().query(chain, { poolAddress: 'not-an-address' }), + (error: unknown) => + error instanceof CCTParamsInvalidError && + error.context.operation === 'getTokenPoolRemotes' && + error.context.param === 'poolAddress', + ) + assert.equal(called, false, 'validation fails before the chain read') + }) + + for (const [label, remoteChainSelector] of [ + ['negative', -1n], + ['above uint64 max', 2n ** 64n], + ['a number, not a bigint', 1 as never], + ] as const) { + it(`rejects a remoteChainSelector that is ${label}, before any RPC`, async () => { + let called = false + const chain = { + getTokenPoolRemotes: () => { + called = true + return Promise.resolve(REMOTES) + }, + } as unknown as EVMChain + + await assert.rejects( + () => new GetTokenPoolRemotes().query(chain, { poolAddress: POOL, remoteChainSelector }), + (error: unknown) => + error instanceof CCTParamsInvalidError && + error.context.operation === 'getTokenPoolRemotes' && + error.context.param === 'remoteChainSelector', + ) + assert.equal(called, false, 'validation fails before the chain read') + }) + } + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/get-token-pool-remotes.ts b/ccip-sdk/src/cct/evm/token-pool/operations/get-token-pool-remotes.ts new file mode 100644 index 000000000..381c1cc55 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/get-token-pool-remotes.ts @@ -0,0 +1,63 @@ +/** + * getTokenPoolRemotes — reads a token pool's per-lane remote configuration + * Delegates to {@link EVMChain.getTokenPoolRemotes}. + * + * @packageDocumentation + */ + +import type { TokenPoolRemote } from '../../../../chain.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { EVMQuery } from '../../query.ts' +import { validateAddress, validateUint64 } from '../../validate.ts' + +/** Parameters for {@link GetTokenPoolRemotes}. */ +export type GetTokenPoolRemotesParams = { + /** + * Token pool contract address to read. + * @remarks Spelled `poolAddress` for consistency with every other CCT pool op, even though + * {@link EVMChain.getTokenPoolRemotes} names the same argument `tokenPool`. + */ + poolAddress: string + /** + * CCIP selector of a single remote chain to read (`uint64`). Omit to scan every lane the pool + * reports through `getSupportedChains()`. + */ + remoteChainSelector?: bigint +} + +/** Result of {@link GetTokenPoolRemotes}: remote-lane configurations keyed by network name. */ +export type GetTokenPoolRemotesResult = Record + +/** + * Reads all, or one selected, remote-chain configurations of an EVM token pool. + * + * @remarks Delegates decoding wholesale to {@link EVMChain.getTokenPoolRemotes}; this class only + * validates params (no RPC) and forwards them. + */ +export class GetTokenPoolRemotes extends EVMQuery< + GetTokenPoolRemotesParams, + GetTokenPoolRemotesResult +> { + readonly name = 'getTokenPoolRemotes' + + /** + * Validates the pool address and, when given, the remote-chain selector; nothing to convert for + * {@link read}. + * @throws {@link CCTParamsInvalidError} if `poolAddress` is not a valid address, or + * `remoteChainSelector` is given and is not a `uint64` + */ + protected prepare(params: GetTokenPoolRemotesParams): GetTokenPoolRemotesParams { + validateAddress(this.name, 'poolAddress', params.poolAddress) + if (params.remoteChainSelector !== undefined) + validateUint64(this.name, 'remoteChainSelector', params.remoteChainSelector) + return params + } + + /** Delegates remote-lane decoding to the shared chain reader, which owns the version branches. */ + protected read( + chain: EVMChain, + { poolAddress, remoteChainSelector }: GetTokenPoolRemotesParams, + ): Promise { + return chain.getTokenPoolRemotes(poolAddress, remoteChainSelector) + } +} diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/get-token-pool-state.test.ts b/ccip-sdk/src/cct/evm/token-pool/operations/get-token-pool-state.test.ts new file mode 100644 index 000000000..2eee5c089 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/get-token-pool-state.test.ts @@ -0,0 +1,360 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { getAddress, makeError, toBeHex } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import { parseTypeAndVersion } from '../../../../utils.ts' +import { + CCTContractTypeInvalidError, + CCTContractVersionUnsupportedError, + CCTParamsInvalidError, +} from '../../../errors.ts' +import { type TokenPoolFamily, TOKEN_POOL_INTERFACES, TokenPoolVersion } from '../contracts.ts' +import { GetTokenPoolState } from './get-token-pool-state.ts' + +const POOL = '0x' + '11'.repeat(20) +const TOKEN = '0x' + '22'.repeat(20) +const ROUTER = '0x' + '33'.repeat(20) +const OWNER = '0x' + '44'.repeat(20) +const RMN_PROXY = '0x' + '55'.repeat(20) +const RATE_LIMIT_ADMIN = '0x' + '66'.repeat(20) +const FEE_ADMIN = '0x' + '77'.repeat(20) +const LOCKBOX = '0x' + '99'.repeat(20) + +const CHAINS = [5009297550715157269n, 16015286601757825753n] + +/** Getters the op reads, as `functionName -> return values` (ABI-encoded on demand). */ +type Reads = Record + +/** `getAllowedFinalityConfig` packs the FCR flag above the 16-bit FTF depth, as bytes4. */ +const FINALITY_SAFE_FLAG = 1 << 16 +const finalityConfig = (allowed: number) => toBeHex(allowed, 4) + +/** + * EVMChain stub: `typeAndVersion` reports `typeAndVersion` (parsed the way the real chain does), + * and the provider answers `eth_call` from `reads`, keyed by selector off the pool's own + * Interface. Any getter absent from `reads` reverts. + */ +function stubChain({ + typeAndVersion = 'BurnMintTokenPool 2.0.0', + family = 'BurnMint', + version = TokenPoolVersion.V2_0_0, + reads = {}, + tokenDecimals = 18, +}: { + typeAndVersion?: string + family?: TokenPoolFamily + /** ABI the stub encodes results with — must match the version `typeAndVersion` reports. */ + version?: TokenPoolVersion + reads?: Reads + /** Decimals `getTokenInfo` reports, which pre-v2.0.0 pools read instead of a pool getter. */ + tokenDecimals?: number +} = {}): EVMChain { + const iface = TOKEN_POOL_INTERFACES[family][version] + const responses = new Map( + Object.entries(reads).map(([fn, values]) => [ + iface.getFunction(fn)!.selector, + iface.encodeFunctionResult(fn, values), + ]), + ) + return { + provider: { + call: async ({ data }: { data: string }) => { + const encoded = responses.get(data.slice(0, 10)) + if (!encoded) + throw makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: null, data }, + invocation: null, + revert: null, + }) + return encoded + }, + }, + logger: { debug() {}, info() {}, warn() {}, error() {} }, + typeAndVersion: () => Promise.resolve(parseTypeAndVersion(typeAndVersion)), + getTokenInfo: () => Promise.resolve({ decimals: tokenDecimals, symbol: 'TKN', name: 'Token' }), + } as unknown as EVMChain +} + +const READS: Reads = { + getToken: [TOKEN], + owner: [OWNER], + getRmnProxy: [RMN_PROXY], + getTokenDecimals: [18], + getSupportedChains: [CHAINS], + getDynamicConfig: [ROUTER, RATE_LIMIT_ADMIN, FEE_ADMIN], + getAllowedFinalityConfig: [finalityConfig(10)], +} + +/** Pre-v2.0.0 getters: router and the rate-limit role stand alone, and there is no fee admin. */ +const LEGACY_READS: Reads = { + getToken: [TOKEN], + owner: [OWNER], + getRouter: [ROUTER], + getRmnProxy: [RMN_PROXY], + getRateLimitAdmin: [RATE_LIMIT_ADMIN], + getSupportedChains: [CHAINS], +} + +describe('GetTokenPoolState (cct/evm token-pool query)', () => { + it('reads a burn-mint pool: token, roles, lanes and allowed finality', async () => { + const state = await new GetTokenPoolState().query(stubChain({ reads: READS }), { + poolAddress: POOL, + }) + + assert.deepEqual(state, { + poolAddress: POOL, + version: '2.0.0', + type: 'BurnMintTokenPool', + token: TOKEN, + tokenDecimals: 18, + router: ROUTER, + owner: OWNER, + rmnProxy: RMN_PROXY, + rateLimitAdmin: RATE_LIMIT_ADMIN, + feeAdmin: FEE_ADMIN, + supportedChains: CHAINS, + finalityDepth: 10, + finalitySafe: false, + }) + }) + + it('reads the lockbox of a lock-release pool', async () => { + const chain = stubChain({ + typeAndVersion: 'LockReleaseTokenPool 2.0.0', + family: 'LockRelease', + reads: { ...READS, getLockBox: [LOCKBOX] }, + }) + + const state = await new GetTokenPoolState().query(chain, { poolAddress: POOL }) + + // narrowing on `version` then `type` is what exposes lockBox — no optional field to check + assert.ok(state.version === '2.0.0' && state.type === 'LockReleaseTokenPool') + assert.equal(state.lockBox, LOCKBOX) + }) + + it('reads a v2.0.0 siloed pool, reporting every field but the lockbox', async () => { + // no no-arg getter in `reads`: a siloed pool declares getLockBox(uint64) instead + const chain = stubChain({ + typeAndVersion: 'SiloedLockReleaseTokenPool 2.0.0', + family: 'LockRelease', + reads: READS, + }) + + const state = await new GetTokenPoolState().query(chain, { poolAddress: POOL }) + + assert.deepEqual(state, { + poolAddress: POOL, + version: '2.0.0', + type: 'SiloedLockReleaseTokenPool', + token: TOKEN, + tokenDecimals: 18, + router: ROUTER, + owner: OWNER, + rmnProxy: RMN_PROXY, + rateLimitAdmin: RATE_LIMIT_ADMIN, + feeAdmin: FEE_ADMIN, + supportedChains: CHAINS, + finalityDepth: 10, + finalitySafe: false, + }) + // per-lane escrow: no single lockbox, so the field is absent rather than zeroed + assert.ok(!('lockBox' in state)) + }) + + it('never calls getLockBox() on a siloed pool, whose escrow is keyed per remote chain', async () => { + const noArgLockBox = + TOKEN_POOL_INTERFACES.LockRelease[TokenPoolVersion.V2_0_0].getFunction( + 'getLockBox()', + )!.selector + const seen: string[] = [] + const chain = stubChain({ + typeAndVersion: 'SiloedLockReleaseTokenPool 2.0.0', + family: 'LockRelease', + reads: READS, + }) + const provider = chain.provider as unknown as { + call: (tx: { data: string }) => Promise + } + const { call } = provider + provider.call = (tx) => { + seen.push(tx.data.slice(0, 10)) + return call(tx) + } + + await new GetTokenPoolState().query(chain, { poolAddress: POOL }) + + assert.ok(!seen.includes(noArgLockBox), 'getLockBox() is not implemented by a siloed pool') + }) + + it('reads router and both admin roles from the single getDynamicConfig call', async () => { + let calls = 0 + const chain = stubChain({ reads: READS }) + const provider = chain.provider as unknown as { + call: (tx: { data: string }) => Promise + } + const { call } = provider + provider.call = (tx) => { + calls++ + return call(tx) + } + + const state = await new GetTokenPoolState().query(chain, { poolAddress: POOL }) + + assert.equal(state.router, ROUTER) + assert.equal(state.rateLimitAdmin, RATE_LIMIT_ADMIN) + assert.ok(state.version === '2.0.0') + assert.equal(state.feeAdmin, FEE_ADMIN) + assert.equal(calls, Object.keys(READS).length, 'one call per getter, none duplicated') + }) + + it('decodes the FCR flag packed above the finality depth', async () => { + const chain = stubChain({ + reads: { ...READS, getAllowedFinalityConfig: [finalityConfig(FINALITY_SAFE_FLAG)] }, + }) + + const state = await new GetTokenPoolState().query(chain, { poolAddress: POOL }) + + assert.ok(state.version === '2.0.0') + assert.equal(state.finalitySafe, true) + assert.equal(state.finalityDepth, 0) + }) + + describe('pre-v2.0.0 pools', () => { + it('reads a v1.5.1 burn-mint pool through the getters that version has', async () => { + const chain = stubChain({ + typeAndVersion: 'BurnMintTokenPool 1.5.1', + version: TokenPoolVersion.V1_5_1, + reads: LEGACY_READS, + }) + + const state = await new GetTokenPoolState().query(chain, { poolAddress: POOL }) + + // no feeAdmin, finality window, or lockbox: none of them exist before v2.0.0 + assert.deepEqual(state, { + poolAddress: POOL, + version: '1.5.1', + type: 'BurnMintTokenPool', + token: TOKEN, + tokenDecimals: 18, + router: ROUTER, + owner: OWNER, + rmnProxy: RMN_PROXY, + rateLimitAdmin: RATE_LIMIT_ADMIN, + supportedChains: CHAINS, + }) + }) + + it('reads a v1.6.1 lock-release pool, which has no lockbox to report', async () => { + const chain = stubChain({ + typeAndVersion: 'LockReleaseTokenPool 1.6.1', + family: 'LockRelease', + version: TokenPoolVersion.V1_6_1, + reads: LEGACY_READS, + }) + + const state = await new GetTokenPoolState().query(chain, { poolAddress: POOL }) + + assert.equal(state.version, '1.6.1') + assert.equal(state.type, 'LockReleaseTokenPool') + // `lockBox` arrives with v2.0.0; narrowing on version is what keeps it off this arm + assert.ok(!('lockBox' in state)) + }) + + it('reads a siloed pool at a legacy version through the legacy reader', async () => { + // The legacy reader only calls getters TokenPool itself declares, so it serves any type. + const chain = stubChain({ + typeAndVersion: 'SiloedLockReleaseTokenPool 1.6.1', + family: 'LockRelease', + version: TokenPoolVersion.V1_6_1, + reads: LEGACY_READS, + }) + + const state = await new GetTokenPoolState().query(chain, { poolAddress: POOL }) + + assert.equal(state.type, 'SiloedLockReleaseTokenPool') + assert.equal(state.version, '1.6.1') + }) + + it('takes decimals from the token at v1.5.0, which has no getTokenDecimals', async () => { + const chain = stubChain({ + typeAndVersion: 'BurnMintTokenPool 1.5.0', + version: TokenPoolVersion.V1_5_0, + reads: LEGACY_READS, + tokenDecimals: 6, + }) + + const state = await new GetTokenPoolState().query(chain, { poolAddress: POOL }) + + assert.equal(state.tokenDecimals, 6) + }) + + it('reads a v1.5.0 AndProxy pool, reporting the normalized base type', async () => { + const chain = stubChain({ + typeAndVersion: 'BurnMintTokenPoolAndProxy 1.5.0', + version: TokenPoolVersion.V1_5_0, + reads: LEGACY_READS, + }) + + const state = await new GetTokenPoolState().query(chain, { poolAddress: POOL }) + + assert.equal(state.type, 'BurnMintTokenPool') + assert.equal(state.version, '1.5.0') + }) + }) + + it('checksums the returned pool address', async () => { + const lowercase = '0x' + 'ab'.repeat(20) + + const state = await new GetTokenPoolState().query(stubChain({ reads: READS }), { + poolAddress: lowercase, + }) + + assert.equal(state.poolAddress, getAddress(lowercase)) + }) + + describe('validation', () => { + it('rejects an invalid pool address before any RPC', async () => { + let probed = false + const chain = stubChain({ reads: READS }) + chain.typeAndVersion = () => { + probed = true + return Promise.resolve(parseTypeAndVersion('BurnMintTokenPool 2.0.0')) + } + + await assert.rejects( + () => new GetTokenPoolState().query(chain, { poolAddress: 'nope' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'getTokenPoolState' && + err.context.param === 'poolAddress', + ) + assert.equal(probed, false, 'validation fails before the typeAndVersion probe') + }) + + it('rejects a pool type outside the supported CCT set', async () => { + const chain = stubChain({ typeAndVersion: 'USDCTokenPoolProxy 2.0.0', reads: READS }) + + await assert.rejects( + () => new GetTokenPoolState().query(chain, { poolAddress: POOL }), + (err: unknown) => err instanceof CCTContractTypeInvalidError, + ) + }) + + it('rejects a supported pool type reporting a version the SDK does not know', async () => { + const chain = stubChain({ typeAndVersion: 'BurnMintTokenPool 9.9.9', reads: READS }) + + await assert.rejects( + () => new GetTokenPoolState().query(chain, { poolAddress: POOL }), + (err: unknown) => + err instanceof CCTContractVersionUnsupportedError && + err.context.contractType === 'BurnMintTokenPool' && + err.context.version === '9.9.9', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/get-token-pool-state.ts b/ccip-sdk/src/cct/evm/token-pool/operations/get-token-pool-state.ts new file mode 100644 index 000000000..e2fe93d92 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/get-token-pool-state.ts @@ -0,0 +1,288 @@ +/** + * getTokenPoolState — reads a token pool's admin state (v1.5.0–v2.0.0): the owner and admin roles + * CCT writes are gated on, which {@link EVMChain.getTokenPoolConfig} (a transfer-flow read) does + * not return. One reader per version generation, since the getters differ. + * + * @packageDocumentation + */ + +import { getAddress } from 'ethers' +import type { TypedContract } from 'ethers-abitype' + +import type { EVMChain } from '../../../../evm/index.ts' +import { resultToObject } from '../../../../evm/types.ts' +import { decodeFinalityAllowed } from '../../../../extra-args.ts' +import BURN_MINT_TOKEN_POOL_V1_5_0_ABI from '../../artifacts/abi/V1_5_0/burn-mint-token-pool-and-proxy.ts' +import BURN_MINT_TOKEN_POOL_V2_0_0_ABI from '../../artifacts/abi/V2_0_0/burn-mint-token-pool.ts' +import LOCK_RELEASE_TOKEN_POOL_V2_0_0_ABI from '../../artifacts/abi/V2_0_0/lock-release-token-pool.ts' +import { EVMQuery, getTypedContract } from '../../query.ts' +import { validateAddress } from '../../validate.ts' +import { + type BurnMintTokenPoolType, + type LockReleaseTokenPoolType, + type TokenPoolType, + TokenPoolVersion, + isLockReleaseTokenPoolType, + resolveTokenPool, +} from '../contracts.ts' + +/** Parameters for {@link GetTokenPoolState}. */ +export interface GetTokenPoolStateParams { + /** Token pool contract address to read. */ + poolAddress: string +} + +/** Admin state every supported pool version reports, however each spells the call. */ +type TokenPoolStateCore = { + /** Address read, checksummed. */ + poolAddress: string + /** Token this pool manages. */ + token: string + /** Local decimals of {@link TokenPoolStateCore.token}. */ + tokenDecimals: number + /** Router the pool accepts ramp calls from. */ + router: string + /** Current pool owner — the signer every CCT pool write is gated on. */ + owner: string + /** RMN proxy the pool checks for curses. */ + rmnProxy: string + /** Address that may change rate limits besides the owner. */ + rateLimitAdmin: string + /** Remote chain selectors configured on the pool. */ + supportedChains: bigint[] +} + +/** + * State of a pre-v2.0.0 pool (v1.5.0–v1.6.1), of any supported type: no fee admin, finality + * window, or lockbox, none of which exist before v2.0.0. + * @remarks A legacy pool's `allowList` and (lock/release) `rebalancer` are transfer-flow and + * liquidity concerns, not admin ones; read those via `cct.chain.getTokenPoolConfig()`. + */ +export type LegacyTokenPoolState = TokenPoolStateCore & { + version: Exclude + type: TokenPoolType +} + +/** Admin state v2.0.0 adds to {@link TokenPoolStateCore}: the fee role and the finality window. */ +type TokenPoolStateCoreV2_0_0 = TokenPoolStateCore & { + version: typeof TokenPoolVersion.V2_0_0 + /** Address that may change token transfer fee config besides the owner. */ + feeAdmin: string + /** Min block confirmations the pool allows for Faster-Than-Finality; `0` when FTF is off. */ + finalityDepth: number + /** Whether the pool allows "safe" finality (FCR). */ + finalitySafe: boolean +} + +/** State of a v2.0.0 burn-* mint pool, which mints/burns the token directly. */ +export type BurnMintTokenPoolStateV2_0_0 = TokenPoolStateCoreV2_0_0 & { + type: BurnMintTokenPoolType +} + +/** State of a v2.0.0 lock/release pool, whose liquidity is escrowed in a single lockbox. */ +export type LockReleaseTokenPoolStateV2_0_0 = TokenPoolStateCoreV2_0_0 & { + type: 'LockReleaseTokenPool' + /** Lockbox escrowing this pool's liquidity. */ + lockBox: string +} + +/** + * State of a v2.0.0 *siloed* lock/release pool — every field its non-siloed sibling reports + * **except** `lockBox`: it escrows per remote chain, so it declares `getLockBox(uint64)` and no + * no-arg `getLockBox()`. Read a lane's escrow with `getLockBox(remoteChainSelector)` against the + * pool directly; this query does not enumerate them. + */ +export type SiloedLockReleaseTokenPoolStateV2_0_0 = TokenPoolStateCoreV2_0_0 & { + type: 'SiloedLockReleaseTokenPool' +} + +/** State of a v2.0.0 pool. Only `type === 'LockReleaseTokenPool'` reports a `lockBox`. */ +export type TokenPoolStateV2_0_0 = + | BurnMintTokenPoolStateV2_0_0 + | LockReleaseTokenPoolStateV2_0_0 + | SiloedLockReleaseTokenPoolStateV2_0_0 + +/** + * Admin state of a token pool: `version === '2.0.0'` gates the roles and finality window that + * version added, and `type === 'LockReleaseTokenPool'` gates its `lockBox`. + */ +export type GetTokenPoolStateResult = LegacyTokenPoolState | TokenPoolStateV2_0_0 + +/** The pre-v2.0.0 getters every legacy version declares in both families. */ +type LegacyTokenPoolGetters = Pick< + TypedContract, + 'getToken' | 'owner' | 'getRouter' | 'getRmnProxy' | 'getRateLimitAdmin' | 'getSupportedChains' +> + +/** + * The v2.0.0 getters both families declare identically: a lock/release handle satisfies this too, + * while `getLockBox` stays out of reach of {@link readTokenPoolV2_0_0}. + */ +type TokenPoolGettersV2_0_0 = Pick< + TypedContract, + | 'getToken' + | 'owner' + | 'getRmnProxy' + | 'getTokenDecimals' + | 'getSupportedChains' + | 'getDynamicConfig' + | 'getAllowedFinalityConfig' +> + +/** + * Reads a pre-v2.0.0 pool, where `router` and the rate-limit role have their own getters. + * @remarks v1.5.0 has no `getTokenDecimals`, so decimals come from the token — the one source + * every legacy version shares. + */ +async function readLegacyTokenPool( + chain: EVMChain, + poolAddress: string, + type: TokenPoolType, + version: LegacyTokenPoolState['version'], +): Promise { + const pool: LegacyTokenPoolGetters = getTypedContract( + chain, + poolAddress, + BURN_MINT_TOKEN_POOL_V1_5_0_ABI, + ) + + const [token, owner, router, rmnProxy, rateLimitAdmin, supportedChains] = await Promise.all([ + resultToObject(pool.getToken()), + resultToObject(pool.owner()), + resultToObject(pool.getRouter()), + resultToObject(pool.getRmnProxy()), + resultToObject(pool.getRateLimitAdmin()), + pool.getSupportedChains(), + ]) + const { decimals } = await chain.getTokenInfo(token) + + return { + poolAddress: getAddress(poolAddress), + version, + type, + token, + tokenDecimals: decimals, + router, + owner, + rmnProxy, + rateLimitAdmin, + supportedChains: [...supportedChains], + } +} + +/** Reads the v2.0.0 getters both families share, leaving each caller to add its own type field. */ +async function readTokenPoolV2_0_0( + pool: TokenPoolGettersV2_0_0, + poolAddress: string, +): Promise { + const [token, owner, rmnProxy, tokenDecimals, supportedChains, dynamicConfig, allowedFinality] = + await Promise.all([ + resultToObject(pool.getToken()), + resultToObject(pool.owner()), + resultToObject(pool.getRmnProxy()), + pool.getTokenDecimals(), + pool.getSupportedChains(), + // left raw: `resultToObject` turns a named Result into an object, breaking this destructure + pool.getDynamicConfig(), + pool.getAllowedFinalityConfig(), + ]) + const [router, rateLimitAdmin, feeAdmin] = dynamicConfig + // `allowedFinality` is a bytes4 packing the FCR flag above the 16-bit FTF depth + const { finalityDepth, finalitySafe } = decodeFinalityAllowed(allowedFinality) + + return { + poolAddress: getAddress(poolAddress), + version: TokenPoolVersion.V2_0_0, + token, + owner, + rmnProxy, + router: resultToObject(router), + rateLimitAdmin: resultToObject(rateLimitAdmin), + feeAdmin: resultToObject(feeAdmin), + // ethers decodes every integer type as bigint, including this `uint8` + tokenDecimals: Number(tokenDecimals), + supportedChains: [...supportedChains], + finalityDepth, + finalitySafe: !!finalitySafe, + } +} + +/** Reads a v2.0.0 burn-* mint pool: the shared state, with no escrow to report. */ +async function readBurnMintTokenPoolV2_0_0( + chain: EVMChain, + poolAddress: string, + type: BurnMintTokenPoolType, +): Promise { + const pool = getTypedContract(chain, poolAddress, BURN_MINT_TOKEN_POOL_V2_0_0_ABI) + return { ...(await readTokenPoolV2_0_0(pool, poolAddress)), type } +} + +/** Reads a v2.0.0 lock/release pool: the shared state, plus `lockBox` for the non-siloed variant. */ +async function readLockReleaseTokenPoolV2_0_0( + chain: EVMChain, + poolAddress: string, + type: LockReleaseTokenPoolType, +): Promise { + // the non-siloed ABI reads a siloed pool too — every shared getter is declared identically, and + // `getLockBox()` is only ever called on the variant that declares it + const pool = getTypedContract(chain, poolAddress, LOCK_RELEASE_TOKEN_POOL_V2_0_0_ABI) + + if (type === 'SiloedLockReleaseTokenPool') + return { ...(await readTokenPoolV2_0_0(pool, poolAddress)), type } + + const [state, lockBox] = await Promise.all([ + readTokenPoolV2_0_0(pool, poolAddress), + resultToObject(pool.getLockBox()), + ]) + return { ...state, type, lockBox } +} + +/** + * Reads a token pool's admin state — `owner`, the rate-limit role, token/router/lanes, plus + * v2.0.0's `feeAdmin`, finality window and `lockBox` — through the vendored ABI of the pool's + * own version. + */ +export class GetTokenPoolState extends EVMQuery { + readonly name = 'getTokenPoolState' + + /** Validates the pool address; nothing to convert for {@link read}. */ + protected prepare(params: GetTokenPoolStateParams): GetTokenPoolStateParams { + validateAddress(this.name, 'poolAddress', params.poolAddress) + return params + } + + /** + * Resolves the pool's type + version, then reads it through the getters that version has. + * @remarks Dispatch is an exhaustive `switch`, not floor-matched like the write ops' encoders: a + * read's shape is bound to the ABI it decodes through, and the v2.0.0 reader reports its version + * as the literal that discriminates {@link GetTokenPoolStateResult}. Floor-matching would make a + * newer pool misreport itself and silently drop any admin field its version added, so a new + * {@link TokenPoolVersion} fails to compile here until it is pointed at a reader. + * @remarks Type only narrows the v2.0.0 result; the legacy getters are declared by `TokenPool` + * itself, so pre-v2.0.0 needs no per-type case. + * @throws {@link CCTContractTypeInvalidError} if the pool's reported type is not a supported one + * @throws {@link CCTContractVersionUnsupportedError} if the reported version is not a known one + */ + protected async read( + chain: EVMChain, + { poolAddress }: GetTokenPoolStateParams, + ): Promise { + const { type, version } = await resolveTokenPool(chain, poolAddress) + + switch (version) { + case TokenPoolVersion.V1_5_0: + case TokenPoolVersion.V1_5_1: + case TokenPoolVersion.V1_6_1: + return readLegacyTokenPool(chain, poolAddress, type, version) + case TokenPoolVersion.V2_0_0: + return isLockReleaseTokenPoolType(type) + ? readLockReleaseTokenPoolV2_0_0(chain, poolAddress, type) + : readBurnMintTokenPoolV2_0_0(chain, poolAddress, type) + default: { + // a new TokenPoolVersion lands here and fails to compile until it gets a reader + const unread: never = version + return unread + } + } + } +} diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/remove-remote-pool.test.ts b/ccip-sdk/src/cct/evm/token-pool/operations/remove-remote-pool.test.ts new file mode 100644 index 000000000..e4d1b4b91 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/remove-remote-pool.test.ts @@ -0,0 +1,391 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError, toBeHex, zeroPadValue } from 'ethers' + +import type { TokenPoolRemote } from '../../../../chain.ts' +import { + CCIPExecTxRevertedError, + CCIPTokenPoolChainConfigNotFoundError, + CCIPWalletInvalidError, +} from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { parseTypeAndVersion } from '../../../../utils.ts' +import { CCTOperationUnsupportedError, CCTParamsInvalidError } from '../../../errors.ts' +import { + type TokenPoolType, + TOKEN_POOL_INTERFACES, + TokenPoolVersion, + getTokenPoolFamily, +} from '../contracts.ts' +import { type RemoveRemotePoolParams, RemoveRemotePool } from './remove-remote-pool.ts' + +const POOL = '0x' + '11'.repeat(20) +const TOKEN = '0x' + '22'.repeat(20) +const ROUTER = '0x' + '33'.repeat(20) +const OWNER = '0x' + '44'.repeat(20) +const RMN_PROXY = '0x' + '55'.repeat(20) +const RATE_LIMIT_ADMIN = '0x' + '66'.repeat(20) +const LOCKBOX = '0x' + '77'.repeat(20) +const NOT_THE_OWNER = '0x' + '88'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +/** The remote pool being removed — registered on the lane in the default stub. */ +const REMOTE_POOL = '0x' + '99'.repeat(20) +/** A remote pool that stays registered. */ +const OTHER_REMOTE_POOL = '0x' + 'aa'.repeat(20) + +const SELECTOR = 5009297550715157269n // ethereum-mainnet +const SOLANA_SELECTOR = 16423721717087811551n // solana-devnet + +/** Calldata built from a fresh Interface — never the SDK's cached one, or this proves nothing. */ +const FRESH = new Interface([ + 'function removeRemotePool(uint64 remoteChainSelector, bytes remotePoolAddress)', +]) +const expectedData = (remotePoolAddress = REMOTE_POOL, selector = SELECTOR) => + FRESH.encodeFunctionData('removeRemotePool', [selector, remotePoolAddress]) + +/** The `getTokenPoolState` getters the owner gate reads, per version generation. */ +function poolReads(version: TokenPoolVersion, type: TokenPoolType, owner: string) { + if (version !== TokenPoolVersion.V2_0_0) + return { + getToken: [TOKEN], + owner: [owner], + getRouter: [ROUTER], + getRmnProxy: [RMN_PROXY], + getRateLimitAdmin: [RATE_LIMIT_ADMIN], + getSupportedChains: [[SELECTOR]], + } + return { + getToken: [TOKEN], + owner: [owner], + getRmnProxy: [RMN_PROXY], + getTokenDecimals: [18], + getSupportedChains: [[SELECTOR]], + getDynamicConfig: [ROUTER, RATE_LIMIT_ADMIN, RATE_LIMIT_ADMIN], + getAllowedFinalityConfig: [toBeHex(0, 4)], + ...(getTokenPoolFamily(type) === 'LockRelease' ? { getLockBox: [LOCKBOX] } : {}), + } +} + +type Calls = { typeAndVersion: number; remotes: Array<[string, bigint | undefined]>; calls: number } + +/** + * EVMChain stub: reports `type`/`version`, answers the owner-gate getters off the pool's own + * Interface, and returns (or throws for) one lane's remotes. + */ +function stubChain({ + type = 'BurnMintTokenPool', + version = TokenPoolVersion.V1_5_1, + owner = OWNER, + remotePools = [REMOTE_POOL] as string[], + remotesError, + seen = { typeAndVersion: 0, remotes: [], calls: 0 }, +}: { + type?: TokenPoolType + version?: TokenPoolVersion + owner?: string + remotePools?: string[] + remotesError?: Error + seen?: Calls +} = {}): EVMChain { + const iface = TOKEN_POOL_INTERFACES[getTokenPoolFamily(type)][version] + const responses = new Map( + Object.entries(poolReads(version, type, owner)).map(([fn, values]) => [ + iface.getFunction(fn)!.selector, + iface.encodeFunctionResult(fn, values), + ]), + ) + const remote: TokenPoolRemote = { + remoteToken: TOKEN, + remotePools, + inboundRateLimiterState: null, + outboundRateLimiterState: null, + } + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + provider: { + call: ({ data }: { data: string }) => { + seen.calls++ + const encoded = responses.get(data.slice(0, 10)) + if (!encoded) + throw makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: null, data }, + invocation: null, + revert: null, + }) + return Promise.resolve(encoded) + }, + }, + typeAndVersion: () => { + seen.typeAndVersion++ + return Promise.resolve(parseTypeAndVersion(`${type} ${version}`)) + }, + getTokenInfo: () => Promise.resolve({ decimals: 18, symbol: 'TKN', name: 'Token' }), + getTokenPoolRemotes: (tokenPool: string, remoteChainSelector?: bigint) => { + seen.remotes.push([tokenPool, remoteChainSelector]) + return remotesError ? Promise.reject(remotesError) : Promise.resolve({ 'a-network': remote }) + }, + nextNonce: () => Promise.resolve(0), + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +function fakeSigner(waitError?: Error, address = OWNER) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => (waitError ? Promise.reject(waitError) : Promise.resolve({ status: 1 })), + }), + } +} + +const op = new RemoveRemotePool() + +function generate(chain: EVMChain, overrides: Partial = {}) { + return op.generate(chain, { + poolAddress: POOL, + remoteChainSelector: SELECTOR, + remotePoolAddress: REMOTE_POOL, + sender: OWNER, + ...overrides, + }) +} + +/** Versions that declare `removeRemotePool`, each with both ABI families. */ +const SUPPORTED = [TokenPoolVersion.V1_5_1, TokenPoolVersion.V1_6_1, TokenPoolVersion.V2_0_0] +const TYPES: TokenPoolType[] = ['BurnMintTokenPool', 'LockReleaseTokenPool'] + +describe('RemoveRemotePool (cct/evm)', () => { + describe('generate', () => { + for (const version of SUPPORTED) { + for (const type of TYPES) { + it(`encodes removeRemotePool(selector, bytes) for a ${type} ${version}`, async () => { + const unsigned = await generate(stubChain({ type, version })) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, POOL) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, expectedData()) + }) + } + } + + it('produces identical calldata for both ABI families', async () => { + const [burnMint, lockRelease] = await Promise.all( + TYPES.map(async (type) => (await generate(stubChain({ type }))).transactions[0]!.data), + ) + assert.equal(burnMint, lockRelease) + assert.equal(burnMint, expectedData()) + }) + + it('accepts a remotePoolAddress without the 0x prefix', async () => { + const unsigned = await generate(stubChain(), { + remotePoolAddress: REMOTE_POOL.slice(2).toUpperCase(), + }) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + }) + + it('encodes a 32-byte non-EVM remote pool address as-is', async () => { + const remotePoolAddress = '0x' + 'cd'.repeat(32) + const unsigned = await generate(stubChain({ remotePools: [remotePoolAddress] }), { + remoteChainSelector: SOLANA_SELECTOR, + remotePoolAddress, + }) + assert.equal(unsigned.transactions[0]!.data, expectedData(remotePoolAddress, SOLANA_SELECTOR)) + }) + + it('omits from, and skips the owner read, when sender is not supplied', async () => { + const seen: Calls = { typeAndVersion: 0, remotes: [], calls: 0 } + const unsigned = await generate(stubChain({ seen }), { sender: undefined }) + assert.equal(unsigned.transactions[0]!.from, undefined) + assert.equal(seen.calls, 0, 'no owner gate without a sender to compare') + }) + + it('scopes the remotes read to the one lane', async () => { + const seen: Calls = { typeAndVersion: 0, remotes: [], calls: 0 } + await generate(stubChain({ seen })) + assert.deepEqual(seen.remotes, [[POOL, SELECTOR]]) + }) + }) + + describe('validation', () => { + for (const [param, value] of [ + ['poolAddress', 'not-an-address'], + // a tx to `0x0` hits no code, so it mines as a successful no-op instead of reverting + ['poolAddress', ZeroAddress], + ['sender', 'not-an-address'], + ['remoteChainSelector', 1 as never], + ['remoteChainSelector', -1n], + ['remoteChainSelector', 2n ** 64n], + ['remotePoolAddress', ''], + ['remotePoolAddress', '0x'], + ['remotePoolAddress', '0xabc'], + ['remotePoolAddress', '0xzz'], + ['remotePoolAddress', 42 as never], + ] as const) { + it(`rejects ${param} = ${String(value)} before any RPC`, async () => { + const seen: Calls = { typeAndVersion: 0, remotes: [], calls: 0 } + await assert.rejects( + () => generate(stubChain({ seen }), { [param]: value }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'removeRemotePool' && + err.context.param === param, + ) + assert.deepEqual([seen.typeAndVersion, seen.remotes.length, seen.calls], [0, 0, 0]) + }) + } + }) + + describe('version dispatch', () => { + it('is unsupported on v1.5.0, which has no removal primitive', async () => { + const seen: Calls = { typeAndVersion: 0, remotes: [], calls: 0 } + await assert.rejects( + () => generate(stubChain({ version: TokenPoolVersion.V1_5_0, seen })), + (err: unknown) => + err instanceof CCTOperationUnsupportedError && + err.context.operation === 'removeRemotePool' && + err.context.version === '1.5.0', + ) + // the encoder is resolved off the single typeAndVersion read, before any further RPC + assert.deepEqual([seen.typeAndVersion, seen.remotes.length, seen.calls], [1, 0, 0]) + }) + + for (const version of SUPPORTED) { + it(`encodes on v${version}`, async () => { + const unsigned = await generate(stubChain({ version })) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + }) + } + + it('covers every known pool version', () => { + assert.deepEqual(Object.values(TokenPoolVersion), [TokenPoolVersion.V1_5_0, ...SUPPORTED]) + }) + }) + + describe('pre-transaction validation', () => { + it('rejects a remote pool that is not registered on the lane', async () => { + await assert.rejects( + () => generate(stubChain({ remotePools: [OTHER_REMOTE_POOL] })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'removeRemotePool' && + err.context.param === 'remotePoolAddress', + ) + }) + + it('matches a registered pool given as left-padded 32-byte bytes', async () => { + // the chain reader returns decoded 20-byte addresses; the caller may pass either form + const unsigned = await generate(stubChain({ remotePools: [REMOTE_POOL] }), { + remotePoolAddress: zeroPadValue(REMOTE_POOL, 32), + }) + assert.equal( + unsigned.transactions[0]!.data, + expectedData(zeroPadValue(REMOTE_POOL, 32).toLowerCase()), + ) + }) + + it('matches a registered pool whose spelling differs only in case', async () => { + const unsigned = await generate( + stubChain({ remotePools: [REMOTE_POOL.toUpperCase().replace('0X', '0x')] }), + ) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + }) + + it('falls back to raw byte comparison when the remote family has no registered codec', async () => { + // `decodeAddress` only knows the families whose chain module is loaded (EVM always is); + // for anything else the undecoded hex is compared + const bytes = '0x' + 'cd'.repeat(32) + const unsigned = await generate(stubChain({ remotePools: [bytes] }), { + remoteChainSelector: SOLANA_SELECTOR, + remotePoolAddress: bytes.toUpperCase().replace('0X', '0x'), + }) + assert.equal(unsigned.transactions[0]!.data, expectedData(bytes, SOLANA_SELECTOR)) + }) + + it('removes one of several registered pools', async () => { + const unsigned = await generate(stubChain({ remotePools: [OTHER_REMOTE_POOL, REMOTE_POOL] })) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + }) + + it('rejects removal on a lane that has no configuration at all', async () => { + const chain = stubChain({ + remotesError: new CCIPTokenPoolChainConfigNotFoundError(POOL, POOL, 'a-network'), + }) + await assert.rejects( + () => generate(chain), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'remotePoolAddress', + ) + }) + + it('propagates any other remotes-read failure', async () => { + const boom = new Error('rpc down') + await assert.rejects(() => generate(stubChain({ remotesError: boom })), boom) + }) + + it('rejects a sender that is not the pool owner', async () => { + await assert.rejects( + () => generate(stubChain({ owner: NOT_THE_OWNER })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'removeRemotePool' && + err.context.param === 'sender', + ) + }) + }) + + describe('execute', () => { + const params = { + poolAddress: POOL, + remoteChainSelector: SELECTOR, + remotePoolAddress: REMOTE_POOL, + } + + it('signs and submits as the owner, resolving to the tx hash', async () => { + assert.deepEqual(await op.execute(stubChain(), { ...params, wallet: fakeSigner() }), { + hash: HASH, + }) + }) + + it('maps an on-chain revert to CCIPExecTxRevertedError', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + ...params, + wallet: fakeSigner(makeError('execution reverted', 'CALL_EXCEPTION')), + }), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && err.context.operation === 'removeRemotePool', + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { ...params, wallet: {} }), + CCIPWalletInvalidError, + ) + }) + + it('rejects a sender that is not the signing wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { ...params, sender: NOT_THE_OWNER, wallet: fakeSigner() }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'removeRemotePool' && + err.context.param === 'sender', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/remove-remote-pool.ts b/ccip-sdk/src/cct/evm/token-pool/operations/remove-remote-pool.ts new file mode 100644 index 000000000..4b943a2eb --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/remove-remote-pool.ts @@ -0,0 +1,131 @@ +/** + * removeRemotePool: de-authorizes one remote pool address on a lane (v1.5.1+). + * + * @remarks **v1.5.1 and newer**, the versions where a lane holds a *set* of remote pools. The + * counterpart to {@link AddRemotePool}, and the last step of a remote-side pool upgrade: add the + * new pool, drain the old, then remove it. v1.5.0 has no removal primitive — its single remote + * pool can only be overwritten via {@link SetRemotePool} — so this op reports itself unsupported + * there rather than emulating a removal. + * + * @packageDocumentation + */ + +import type { Interface } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { + TokenPoolVersion, + assertPoolOwner, + getTokenPoolInterface, + resolveEncoder, + resolveTokenPool, +} from '../contracts.ts' +import { + type ParsedRemotePoolParams, + type RemotePoolParams, + isRegisteredRemotePool, + parseRemotePoolParams, + readRegisteredRemotePools, +} from '../remote-pool.ts' + +/** + * Parameters for {@link RemoveRemotePool} — see {@link RemotePoolParams}; `remotePoolAddress` is + * the remote chain's pool address as hex bytes, removed from the lane's existing set. + */ +export type RemoveRemotePoolParams = RemotePoolParams + +/** {@link RemoveRemotePoolParams} as {@link RemoveRemotePool.parse} leaves it. */ +type ParsedRemoveRemotePoolParams = ParsedRemotePoolParams + +/** Encodes `removeRemotePool` calldata against the resolved pool {@link Interface}. */ +type Encoder = (iface: Interface, params: ParsedRemoveRemotePoolParams) => UnsignedEVMTx + +const encodeRemoveRemotePool: Encoder = ( + iface, + { poolAddress, remoteChainSelector, remotePoolAddress }, +) => + callTx( + poolAddress, + iface.encodeFunctionData('removeRemotePool', [remoteChainSelector, remotePoolAddress]), + ) + +/** De-authorizes a remote pool on one lane of a v1.5.1+ pool via `removeRemotePool`. */ +export class RemoveRemotePool extends EVMOperation< + RemoveRemotePoolParams, + ParsedRemoveRemotePoolParams +> { + readonly name = 'removeRemotePool' + + /** + * v1.5.1 and up, where the function was introduced and has not changed since — one entry + * covers v1.6.1 and v2.0.0 by {@link resolveEncoder}'s floor-match. No `null` ceiling is + * needed at the bottom: v1.5.0 matches nothing at or below itself and is reported unsupported + * for free. + */ + private readonly encoders: Partial> = { + [TokenPoolVersion.V1_5_1]: encodeRemoveRemotePool, + } + + /** + * Validates the pool address, lane selector and remote pool bytes before any RPC, keeping the + * parsed `remotePoolAddress` so {@link buildUnsigned} checks and encodes it without re-parsing. + */ + protected override parse(params: RemoveRemotePoolParams): ParsedRemoveRemotePoolParams { + return parseRemotePoolParams(this.name, params) + } + + /** + * Resolves the pool's version (rejecting v1.5.0), confirms `sender` owns the pool, then requires + * the address to actually be registered on this lane: the lane's remote pools are read scoped to + * this one selector, and removing an address that is not among them would revert on-chain + * (`InvalidRemotePoolForChain`). An unconfigured lane reads as having none — see + * {@link readRegisteredRemotePools} — and is rejected the same way. + * @throws {@link CCTOperationUnsupportedError} if the pool is v1.5.0 + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the pool owner, or if + * `remotePoolAddress` is not currently registered on this lane + */ + protected async buildUnsigned( + chain: EVMChain, + params: ParsedRemoveRemotePoolParams, + ): Promise { + const { type, version } = await resolveTokenPool(chain, params.poolAddress) + // resolved before any further RPC, so an unsupported version fails on one call + const encode = resolveEncoder(this.encoders, version, this.name) + // owner-gated on-chain; surface it as a param error here instead of an on-chain revert + if (params.sender !== undefined) + await assertPoolOwner(this.name, chain, params.poolAddress, params.sender) + + const registered = await readRegisteredRemotePools(chain, params) + if (!isRegisteredRemotePool(registered, params.remotePoolAddress, params.remoteChainSelector)) + throw new CCTParamsInvalidError( + this.name, + 'remotePoolAddress', + `is not registered on chain selector ${params.remoteChainSelector} (registered: ${registered.join(', ') || 'none'}); removing it reverts`, + ) + + chain.logger.debug( + `${this.name}: pool = ${params.poolAddress}, lane = ${params.remoteChainSelector}, registered = ${registered.length}`, + ) + return encode(getTokenPoolInterface(type, version), params) + } + + /** + * Signs and submits as the pool owner, defaulting `sender` to the signing wallet — the only + * address that can satisfy {@link buildUnsigned}'s owner check for a broadcast tx. See + * {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is rejected. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address, or + * if any other param is invalid (see {@link buildUnsigned}) + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/set-chain-rate-limiter-configs.test.ts b/ccip-sdk/src/cct/evm/token-pool/operations/set-chain-rate-limiter-configs.test.ts new file mode 100644 index 000000000..824cc27c9 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/set-chain-rate-limiter-configs.test.ts @@ -0,0 +1,635 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { parseTypeAndVersion } from '../../../../utils.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { type TokenPoolFamily, TOKEN_POOL_INTERFACES, TokenPoolVersion } from '../contracts.ts' +import { + type ChainRateLimitUpdate, + type SetChainRateLimiterConfigsParams, + SetChainRateLimiterConfigs, +} from './set-chain-rate-limiter-configs.ts' + +const POOL = '0x' + '11'.repeat(20) +const TOKEN = '0x' + '22'.repeat(20) +const ROUTER = '0x' + '33'.repeat(20) +const OWNER = '0x' + '44'.repeat(20) +const RMN_PROXY = '0x' + '55'.repeat(20) +const RATE_LIMIT_ADMIN = '0x' + '66'.repeat(20) +const FEE_ADMIN = '0x' + '77'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +const ETHEREUM = 5009297550715157269n +const SEPOLIA = 16015286601757825753n + +/** Two lanes: one enabled with amounts, one disabled with its amounts omitted. */ +const UPDATES: ChainRateLimitUpdate[] = [ + { + remoteChainSelector: ETHEREUM, + outboundRateLimiterConfig: { enabled: true, capacity: 1_000_000n, rate: 100n }, + inboundRateLimiterConfig: { enabled: true, capacity: 2_000_000n, rate: 200n }, + }, + { + remoteChainSelector: SEPOLIA, + outboundRateLimiterConfig: { enabled: false }, + inboundRateLimiterConfig: { enabled: false }, + }, +] + +/** + * Expected v1.5.1/v1.6.1 calldata, from a FRESH Interface written off the human-readable + * signature — never the SDK's own cached one, which would make this a tautology. + */ +const BATCH_DATA = new Interface([ + 'function setChainRateLimiterConfigs(uint64[] remoteChainSelectors, (bool isEnabled, uint128 capacity, uint128 rate)[] outboundConfigs, (bool isEnabled, uint128 capacity, uint128 rate)[] inboundConfigs)', +]).encodeFunctionData('setChainRateLimiterConfigs', [ + [ETHEREUM, SEPOLIA], + [ + [true, 1_000_000n, 100n], + [false, 0n, 0n], + ], + [ + [true, 2_000_000n, 200n], + [false, 0n, 0n], + ], +]) + +/** Expected v2.0.0 calldata, likewise from a fresh Interface; `fastFinality` defaults to false. */ +const v2Data = (fastFinality: [boolean, boolean] = [false, false]) => + new Interface([ + 'function setRateLimitConfig((uint64 remoteChainSelector, bool fastFinality, (bool isEnabled, uint128 capacity, uint128 rate) outboundRateLimiterConfig, (bool isEnabled, uint128 capacity, uint128 rate) inboundRateLimiterConfig)[] rateLimitConfigArgs)', + ]).encodeFunctionData('setRateLimitConfig', [ + [ + [ETHEREUM, fastFinality[0], [true, 1_000_000n, 100n], [true, 2_000_000n, 200n]], + [SEPOLIA, fastFinality[1], [false, 0n, 0n], [false, 0n, 0n]], + ], + ]) + +/** + * Expected v1.5.0 calldata, from a fresh Interface off the singular signature. v1.5.0 sets one + * lane per call, so this carries only the first of the two {@link UPDATES} lanes. + */ +const SINGLE_DATA_V1_5_0 = new Interface([ + 'function setChainRateLimiterConfig(uint64 remoteChainSelector, (bool isEnabled, uint128 capacity, uint128 rate) outboundConfig, (bool isEnabled, uint128 capacity, uint128 rate) inboundConfig)', +]).encodeFunctionData('setChainRateLimiterConfig', [ + ETHEREUM, + [true, 1_000_000n, 100n], + [true, 2_000_000n, 200n], +]) + +/** Getters the role check reads, as `functionName -> return values` (ABI-encoded on demand). */ +type Reads = Record + +/** v2.0.0 pool state: the rate-limit role comes out of `getDynamicConfig`. */ +const readsV2_0_0 = (rateLimitAdmin = RATE_LIMIT_ADMIN, owner = OWNER): Reads => ({ + getToken: [TOKEN], + owner: [owner], + getRmnProxy: [RMN_PROXY], + getTokenDecimals: [18], + getSupportedChains: [[ETHEREUM, SEPOLIA]], + getDynamicConfig: [ROUTER, rateLimitAdmin, FEE_ADMIN], + getAllowedFinalityConfig: ['0x00000000'], +}) + +/** + * Legacy (pre-2.0.0) pool state: the rate-limit role has its own standalone `getRateLimitAdmin()` + * getter, decoded as a bare address rather than out of a `getDynamicConfig` triple. + */ +const readsLegacy = (rateLimitAdmin = RATE_LIMIT_ADMIN, owner = OWNER): Reads => ({ + getToken: [TOKEN], + owner: [owner], + getRouter: [ROUTER], + getRmnProxy: [RMN_PROXY], + getRateLimitAdmin: [rateLimitAdmin], + getSupportedChains: [[ETHEREUM, SEPOLIA]], +}) + +/** + * Just the two getters `buildUnsigned`'s owner-or-rateLimitAdmin pre-flight reads, per version + * generation. The default for {@link stubChain}: since the role check moved out of `execute` and + * into `buildUnsigned`, every `generate` with a `sender` needs them answered. + */ +const roleReads = ( + version: TokenPoolVersion, + { + owner = OWNER, + rateLimitAdmin = RATE_LIMIT_ADMIN, + }: { owner?: string; rateLimitAdmin?: string } = {}, +): Reads => + version === TokenPoolVersion.V2_0_0 + ? { owner: [owner], getDynamicConfig: [ROUTER, rateLimitAdmin, FEE_ADMIN] } + : { owner: [owner], getRateLimitAdmin: [rateLimitAdmin] } + +const POOL_TYPE: Record = { + BurnMint: 'BurnMintTokenPool', + LockRelease: 'LockReleaseTokenPool', +} + +/** + * EVMChain stub: `typeAndVersion` reports the pool's own, and the provider answers `eth_call` + * from `reads`, keyed by selector off that version's Interface. Any getter absent from `reads` + * reverts, so a test that supplies none proves no RPC read was attempted. + */ +function stubChain({ + family = 'BurnMint', + version = TokenPoolVersion.V2_0_0, + reads = roleReads(version), + onCall, +}: { + family?: TokenPoolFamily + version?: TokenPoolVersion + reads?: Reads + onCall?: () => void +} = {}): EVMChain { + const iface = TOKEN_POOL_INTERFACES[family][version] + const responses = new Map( + Object.entries(reads).map(([fn, values]) => [ + iface.getFunction(fn)!.selector, + iface.encodeFunctionResult(fn, values), + ]), + ) + return { + provider: { + call: async ({ data }: { data: string }) => { + onCall?.() + const encoded = responses.get(data.slice(0, 10)) + if (!encoded) + throw makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: null, data }, + invocation: null, + revert: null, + }) + return encoded + }, + }, + logger: { debug() {}, info() {}, warn() {}, error() {} }, + typeAndVersion: () => { + onCall?.() + return Promise.resolve(parseTypeAndVersion(`${POOL_TYPE[family]} ${version}`)) + }, + getTokenInfo: () => Promise.resolve({ decimals: 18, symbol: 'TKN', name: 'Token' }), + nextNonce: async () => 0, + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +function fakeSigner(address = OWNER, waitError?: Error) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => (waitError ? Promise.reject(waitError) : Promise.resolve({ status: 1 })), + }), + } +} + +const op = new SetChainRateLimiterConfigs() + +function generate(chain: EVMChain, overrides: Partial = {}) { + return op.generate(chain, { + poolAddress: POOL, + updates: UPDATES, + sender: OWNER, + ...overrides, + }) +} + +describe('SetChainRateLimiterConfigs (cct/evm)', () => { + describe('generate', () => { + for (const family of ['BurnMint', 'LockRelease'] as const) { + for (const version of [TokenPoolVersion.V1_5_1, TokenPoolVersion.V1_6_1] as const) { + it(`encodes the batch setChainRateLimiterConfigs for a ${version} ${family} pool`, async () => { + const unsigned = await generate(stubChain({ family, version })) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, POOL) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, BATCH_DATA) + }) + } + + it(`encodes setRateLimitConfig for a 2.0.0 ${family} pool`, async () => { + const unsigned = await generate(stubChain({ family, version: TokenPoolVersion.V2_0_0 })) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, POOL) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, v2Data()) + }) + } + + it('encodes identically for the BurnMint and LockRelease families', async () => { + for (const version of [ + TokenPoolVersion.V1_5_1, + TokenPoolVersion.V1_6_1, + TokenPoolVersion.V2_0_0, + ] as const) { + const burnMint = await generate(stubChain({ family: 'BurnMint', version })) + const lockRelease = await generate(stubChain({ family: 'LockRelease', version })) + assert.equal(burnMint.transactions[0]!.data, lockRelease.transactions[0]!.data) + } + }) + + it('carries per-entry fastFinality on a 2.0.0 pool', async () => { + const unsigned = await generate(stubChain({ version: TokenPoolVersion.V2_0_0 }), { + updates: [ + { ...UPDATES[0]!, fastFinality: true }, + { ...UPDATES[1]!, fastFinality: false }, + ], + }) + assert.equal(unsigned.transactions[0]!.data, v2Data([true, false])) + }) + + it('omits from when sender is not supplied', async () => { + const unsigned = await generate(stubChain(), { sender: undefined }) + assert.equal(unsigned.transactions[0]!.from, undefined) + }) + }) + + /** + * The offline / multisig builder is gated on the same owner-OR-rateLimitAdmin disjunction as + * `execute`. Before this lived in `buildUnsigned`, `generateUnsignedSetChainRateLimiterConfigs` + * with an arbitrary `sender` issued zero `eth_call`s and handed back a fully-formed transaction + * with an unauthorized `from` — which reverts `Unauthorized` only after review and signing. + */ + describe('generate role pre-flight', () => { + for (const version of [ + TokenPoolVersion.V1_5_1, + TokenPoolVersion.V1_6_1, + TokenPoolVersion.V2_0_0, + ] as const) { + it(`rejects a sender that is neither the owner nor the rateLimitAdmin on v${version}`, async () => { + await assert.rejects( + () => generate(stubChain({ version }), { sender: '0x' + '88'.repeat(20) }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setChainRateLimiterConfigs' && + err.context.param === 'sender', + ) + }) + + it(`accepts the rateLimitAdmin as sender on v${version}`, async () => { + const unsigned = await generate(stubChain({ version }), { sender: RATE_LIMIT_ADMIN }) + assert.equal(unsigned.transactions[0]!.from, RATE_LIMIT_ADMIN) + }) + } + + it('does not let a zero-address sender match an unset rateLimitAdmin', async () => { + await assert.rejects( + () => + generate( + stubChain({ + reads: roleReads(TokenPoolVersion.V2_0_0, { rateLimitAdmin: ZeroAddress }), + }), + { + sender: ZeroAddress, + }, + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setChainRateLimiterConfigs' && + err.context.param === 'sender', + ) + }) + + it('skips the role reads entirely when sender is omitted', async () => { + let calls = 0 + // no `owner`/`getDynamicConfig` answers at all: any role read would revert + const unsigned = await generate(stubChain({ reads: {}, onCall: () => calls++ }), { + sender: undefined, + }) + assert.equal(unsigned.transactions[0]!.data, v2Data()) + assert.equal(calls, 1, 'only the typeAndVersion probe') + }) + }) + + describe('validation', () => { + const cases: { + name: string + param: string + overrides: Partial + /** Set when only the version-specific encoder can reject it (so one RPC is expected). */ + version?: TokenPoolVersion + }[] = [ + { name: 'an invalid poolAddress', param: 'poolAddress', overrides: { poolAddress: 'nope' } }, + // a tx to `0x0` hits no code, so it would mine as a successful no-op rather than reverting + { + name: 'the zero poolAddress', + param: 'poolAddress', + overrides: { poolAddress: ZeroAddress }, + }, + // `.map` skips holes, so without the density guard this used to reach ethers as `undefined` + { + name: 'a hole in updates', + param: 'updates[1]', + overrides: { + updates: (() => { + const sparse = [UPDATES[0]!] + sparse[2] = UPDATES[1]! + return sparse + })(), + }, + }, + { name: 'an invalid sender', param: 'sender', overrides: { sender: 'nope' } }, + { name: 'empty updates', param: 'updates', overrides: { updates: [] } }, + { + name: 'a non-array updates', + param: 'updates', + overrides: { updates: undefined }, + }, + { + name: 'a duplicate remoteChainSelector', + param: 'updates[1].remoteChainSelector', + overrides: { updates: [UPDATES[0]!, { ...UPDATES[1]!, remoteChainSelector: ETHEREUM }] }, + }, + { + name: 'a non-uint64 remoteChainSelector', + param: 'updates[0].remoteChainSelector', + overrides: { updates: [{ ...UPDATES[0]!, remoteChainSelector: -1n }] }, + }, + { + name: 'rate above capacity while enabled', + param: 'updates[0].outboundRateLimiterConfig.rate', + overrides: { + updates: [ + { + ...UPDATES[0]!, + outboundRateLimiterConfig: { enabled: true, capacity: 10n, rate: 11n }, + }, + ], + }, + }, + { + name: 'a non-zero capacity while disabled', + param: 'updates[0].inboundRateLimiterConfig', + overrides: { + updates: [{ ...UPDATES[0]!, inboundRateLimiterConfig: { enabled: false, capacity: 1n } }], + }, + }, + { + name: 'a missing enabled discriminant', + param: 'updates[0].inboundRateLimiterConfig.enabled', + overrides: { + updates: [ + { + ...UPDATES[0]!, + inboundRateLimiterConfig: + {} as unknown as ChainRateLimitUpdate['inboundRateLimiterConfig'], + }, + ], + }, + }, + { + name: 'a non-boolean fastFinality', + param: 'updates[0].fastFinality', + overrides: { + updates: [{ ...UPDATES[0]!, fastFinality: 'yes' as unknown as boolean }], + }, + }, + { + name: 'fastFinality on a pre-2.0.0 pool', + param: 'updates[0].fastFinality', + overrides: { updates: [{ ...UPDATES[0]!, fastFinality: true }] }, + version: TokenPoolVersion.V1_5_1, + }, + { + name: 'fastFinality: false on a pre-2.0.0 pool', + param: 'updates[0].fastFinality', + overrides: { updates: [{ ...UPDATES[0]!, fastFinality: false }] }, + version: TokenPoolVersion.V1_5_1, + }, + ] + + for (const { name, param, overrides, version } of cases) { + it(`rejects ${name}`, async () => { + let calls = 0 + await assert.rejects( + () => generate(stubChain({ version, onCall: () => calls++ }), overrides), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setChainRateLimiterConfigs' && + err.context.param === param, + ) + // params-only failures short-circuit before any RPC; the version-gated ones need exactly + // the one `typeAndVersion` probe that resolved the encoder + assert.equal(calls, version === undefined ? 0 : 1) + }) + } + }) + + describe('version dispatch', () => { + it('encodes the singular setChainRateLimiterConfig for a 1.5.0 pool, one lane per tx', async () => { + const unsigned = await generate(stubChain({ version: TokenPoolVersion.V1_5_0 }), { + updates: [UPDATES[0]!], + }) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, POOL) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, SINGLE_DATA_V1_5_0) + }) + + it('rejects a multi-lane batch on a 1.5.0 pool rather than fanning out to N transactions', async () => { + await assert.rejects( + () => generate(stubChain({ version: TokenPoolVersion.V1_5_0 })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setChainRateLimiterConfigs' && + err.context.param === 'updates', + ) + }) + + for (const version of [ + TokenPoolVersion.V1_5_1, + TokenPoolVersion.V1_6_1, + TokenPoolVersion.V2_0_0, + ] as const) { + it(`supports a ${version} pool`, async () => { + const unsigned = await generate(stubChain({ version })) + assert.equal( + unsigned.transactions[0]!.data, + version === TokenPoolVersion.V2_0_0 ? v2Data() : BATCH_DATA, + ) + }) + } + }) + + describe('execute', () => { + const params = { poolAddress: POOL, updates: UPDATES } + + it('signs and submits as the pool owner, resolving to the tx hash', async () => { + assert.deepEqual( + await op.execute(stubChain({ reads: readsV2_0_0() }), { + ...params, + wallet: fakeSigner(OWNER), + }), + { hash: HASH }, + ) + }) + + it('accepts the rateLimitAdmin as well as the owner', async () => { + assert.deepEqual( + await op.execute(stubChain({ reads: readsV2_0_0() }), { + ...params, + wallet: fakeSigner(RATE_LIMIT_ADMIN), + }), + { hash: HASH }, + ) + }) + + it('accepts a legacy (1.5.1) pool, reading its standalone getRateLimitAdmin', async () => { + assert.deepEqual( + await op.execute( + stubChain({ + version: TokenPoolVersion.V1_5_1, + reads: { + getToken: [TOKEN], + owner: [OWNER], + getRouter: [ROUTER], + getRmnProxy: [RMN_PROXY], + getRateLimitAdmin: [RATE_LIMIT_ADMIN], + getSupportedChains: [[ETHEREUM, SEPOLIA]], + }, + }), + { ...params, wallet: fakeSigner(RATE_LIMIT_ADMIN) }, + ), + { hash: HASH }, + ) + }) + + it('rejects a sender that is neither the owner nor the rateLimitAdmin', async () => { + await assert.rejects( + () => + op.execute(stubChain({ reads: readsV2_0_0() }), { + ...params, + wallet: fakeSigner('0x' + '88'.repeat(20)), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setChainRateLimiterConfigs' && + err.context.param === 'sender', + ) + }) + + it('does not let a zero-address sender match an unset rateLimitAdmin', async () => { + await assert.rejects( + () => + op.execute(stubChain({ reads: readsV2_0_0(ZeroAddress) }), { + ...params, + wallet: fakeSigner(ZeroAddress), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setChainRateLimiterConfigs' && + err.context.param === 'sender', + ) + }) + + it('rejects a sender that differs from the signing wallet', async () => { + await assert.rejects( + () => + op.execute(stubChain({ reads: readsV2_0_0() }), { + ...params, + sender: RATE_LIMIT_ADMIN, + wallet: fakeSigner(OWNER), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setChainRateLimiterConfigs' && + err.context.param === 'sender', + ) + }) + + it('maps an on-chain revert to CCIPExecTxRevertedError', async () => { + await assert.rejects( + () => + op.execute(stubChain({ reads: readsV2_0_0() }), { + ...params, + wallet: fakeSigner(OWNER, makeError('execution reverted', 'CALL_EXCEPTION')), + }), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && + err.context.operation === 'setChainRateLimiterConfigs', + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => op.execute(stubChain({ reads: readsV2_0_0() }), { ...params, wallet: {} }), + CCIPWalletInvalidError, + ) + }) + }) + + /** + * The legacy `getRateLimitAdmin()` branch of the role read. Every case in `execute` above runs + * on a 2.0.0 stub, where `rateLimitAdmin` is instead decoded out of `getDynamicConfig()`'s + * `(router, rateLimitAdmin, feeAdmin)` triple — so the pre-2.0.0 getter and its single-address + * decode would otherwise have no coverage, including the zero-address guard. + */ + describe('execute on a legacy (pre-2.0.0) pool', () => { + const params = { poolAddress: POOL, updates: UPDATES } + const legacyPool = (reads: Reads) => stubChain({ version: TokenPoolVersion.V1_6_1, reads }) + + it('accepts the rateLimitAdmin read from the standalone getRateLimitAdmin()', async () => { + assert.deepEqual( + await op.execute(legacyPool(readsLegacy()), { + ...params, + wallet: fakeSigner(RATE_LIMIT_ADMIN), + }), + { hash: HASH }, + ) + }) + + it('accepts the pool owner', async () => { + assert.deepEqual( + await op.execute(legacyPool(readsLegacy()), { ...params, wallet: fakeSigner(OWNER) }), + { hash: HASH }, + ) + }) + + it('rejects a sender that is neither the owner nor the rateLimitAdmin', async () => { + await assert.rejects( + () => + op.execute(legacyPool(readsLegacy()), { + ...params, + wallet: fakeSigner('0x' + '88'.repeat(20)), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setChainRateLimiterConfigs' && + err.context.param === 'sender', + ) + }) + + it('does not let a zero-address sender match an unset rateLimitAdmin', async () => { + await assert.rejects( + () => + op.execute(legacyPool(readsLegacy(ZeroAddress)), { + ...params, + wallet: fakeSigner(ZeroAddress), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setChainRateLimiterConfigs' && + err.context.param === 'sender', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/set-chain-rate-limiter-configs.ts b/ccip-sdk/src/cct/evm/token-pool/operations/set-chain-rate-limiter-configs.ts new file mode 100644 index 000000000..e6581139b --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/set-chain-rate-limiter-configs.ts @@ -0,0 +1,353 @@ +/** + * setChainRateLimiterConfigs — sets the inbound/outbound rate limits of one or more configured + * lanes on a token pool, in a single transaction. + * + * @remarks Every supported version is served by its own entry point, keeping the + * one-op-one-transaction invariant every CCT write holds: v1.5.1/v1.6.1 encode the batch + * `setChainRateLimiterConfigs(uint64[], Config[], Config[])`, v2.0.0 the reshaped + * `setRateLimitConfig(RateLimitConfigArgs[])`, and v1.5.0 — which ships only the singular + * `setChainRateLimiterConfig(uint64, Config, Config)` — that call. Because v1.5.0 sets one lane + * per transaction, a v1.5.0 pool accepts only a single-element `updates`; a multi-lane batch is + * rejected with {@link CCTParamsInvalidError} rather than fanned out into N transactions. + * + * @packageDocumentation + */ + +import { type Interface, ZeroAddress, getAddress } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { validateArray, validateNonZeroAddress, validateUint64 } from '../../validate.ts' +import { + TokenPoolVersion, + getTokenPoolInterface, + readTokenPoolOwner, + readTokenPoolRateLimitAdmin, + resolveEncoder, + resolveTokenPool, +} from '../contracts.ts' +import { + type ParsedRateLimitConfig, + type RateLimitConfig, + parseRateLimitConfig, +} from '../rate-limit.ts' + +/** + * New rate limits for one already-configured lane. + * + * @remarks The two config fields are deliberately spelled `inboundRateLimiterConfig` / + * `outboundRateLimiterConfig`, matching the Solana `ChainUpdate` in + * `cct/solana/token-pool/operations/apply-chain-updates.ts`, so the very same config objects can be + * passed to `applyChainUpdates` and to this op. + * + * This op only *updates* limits — it does not add a lane. A selector the pool has no chain config + * for reverts on-chain (`NonExistentChain`); add it first with the pool's chain-update op. + */ +export type ChainRateLimitUpdate = { + /** CCIP selector of the already-configured remote chain (`uint64`). */ + remoteChainSelector: bigint + /** Limit on tokens received from the remote chain. */ + inboundRateLimiterConfig: RateLimitConfig + /** Limit on tokens sent to the remote chain. */ + outboundRateLimiterConfig: RateLimitConfig + /** + * Whether this entry configures the lane's *fast-finality* (FTF) buckets rather than its + * finalized ones. **v2.0.0 only** — the field does not exist in the pre-2.0.0 ABIs, so setting it + * (to either value) on a v1.5.1/v1.6.1 pool is rejected instead of silently dropped. Defaults to + * `false` on v2.0.0. + */ + fastFinality?: boolean +} + +/** Parameters for {@link SetChainRateLimiterConfigs}. */ +export type SetChainRateLimiterConfigsParams = { + /** Token pool contract whose lane limits are being set. */ + poolAddress: string + /** Lanes to re-limit; at least one, with no repeated `remoteChainSelector`. */ + updates: ChainRateLimitUpdate[] + /** + * Pool `owner` or `rateLimitAdmin` — the two roles the pools accept for rate-limit writes. Sets + * `tx.from` for offline / multisig signing; {@link SetChainRateLimiterConfigs.execute} + * additionally checks it on-chain. + */ + sender?: string +} + +/** A {@link ChainRateLimitUpdate} with both directions parsed and its `fastFinality` resolved. */ +type ParsedChainRateLimitUpdate = { + remoteChainSelector: bigint + inbound: ParsedRateLimitConfig + outbound: ParsedRateLimitConfig + fastFinality: boolean +} + +/** + * Validates `updates` and resolves every entry: non-empty, selectors distinct `uint64`s, both + * directions through {@link parseRateLimitConfig}. + * @param operation - Operation name, for the error context. + * @param updates - The caller-supplied value, unvalidated. + * @param allowFastFinality - Whether the resolved pool version has the per-entry `fastFinality` + * flag (v2.0.0 and up). When `false`, an entry that sets it at all is rejected. + * @param version - Resolved pool version, or `null` pre-RPC, which skips the stricter + * v1.5.0/v1.5.1 rate bound. + * @throws {@link CCTParamsInvalidError} if `updates` is not a non-empty array, an entry is not an + * object, a selector repeats or is not a `uint64`, `fastFinality` is not a boolean (or is set on a + * version without it), or either direction's config is invalid for `version` + */ +function parseUpdates( + operation: string, + updates: unknown, + allowFastFinality: boolean, + version: TokenPoolVersion | null, +): ParsedChainRateLimitUpdate[] { + validateArray(operation, 'updates', updates, 1) + + const seen = new Set() + return updates.map((update, i) => { + const path = `updates[${i}]` + if (typeof update !== 'object' || update === null) + throw new CCTParamsInvalidError(operation, path, 'must be a chain rate-limit update') + + const { + remoteChainSelector, + inboundRateLimiterConfig, + outboundRateLimiterConfig, + fastFinality, + } = update as Partial + + validateUint64(operation, `${path}.remoteChainSelector`, remoteChainSelector) + if (seen.has(remoteChainSelector)) + throw new CCTParamsInvalidError( + operation, + `${path}.remoteChainSelector`, + `is a duplicate of an earlier update (${remoteChainSelector}); each lane may appear only once`, + ) + seen.add(remoteChainSelector) + + if (fastFinality !== undefined) { + if (!allowFastFinality) + throw new CCTParamsInvalidError( + operation, + `${path}.fastFinality`, + 'is only supported from pool version 2.0.0; omit it for older pools', + ) + if (typeof fastFinality !== 'boolean') + throw new CCTParamsInvalidError(operation, `${path}.fastFinality`, 'must be a boolean') + } + + return { + remoteChainSelector, + inbound: parseRateLimitConfig( + operation, + `${path}.inboundRateLimiterConfig`, + inboundRateLimiterConfig, + version, + ), + outbound: parseRateLimitConfig( + operation, + `${path}.outboundRateLimiterConfig`, + outboundRateLimiterConfig, + version, + ), + fastFinality: fastFinality ?? false, + } + }) +} + +/** + * Encodes the batch rate-limit call against the resolved pool {@link Interface}. + * @remarks `version` is the pool's *actual* resolved version, not the encoder's floor: the v1.5.1 + * encoder serves both v1.5.1 and v1.6.1, whose enabled-bucket rate bounds differ, so it has to be + * told which one it is encoding for. + */ +type Encoder = ( + iface: Interface, + params: SetChainRateLimiterConfigsParams, + version: TokenPoolVersion, +) => UnsignedEVMTx + +/** The on-chain `RateLimiter.Config` tuple: `enabled` maps to the ABI's `isEnabled`. */ +type RateLimiterConfigTuple = [isEnabled: boolean, capacity: bigint, rate: bigint] + +const toTuple = ({ enabled, capacity, rate }: ParsedRateLimitConfig): RateLimiterConfigTuple => [ + enabled, + capacity, + rate, +] + +/** + * v1.5.0: `setChainRateLimiterConfig(uint64, Config outbound, Config inbound)` — the singular + * entry point, one lane per call, so a v1.5.0 pool accepts only a single-element `updates`. A + * multi-lane batch is rejected here rather than fanned out into N transactions, which would break + * the one-op-one-transaction invariant. No `fastFinality` at this version. + */ +const encodeSingleConfigV1_5_0: Encoder = (iface, { poolAddress, updates }, version) => { + const parsed = parseUpdates('setChainRateLimiterConfigs', updates, false, version) + if (parsed.length !== 1) + throw new CCTParamsInvalidError( + 'setChainRateLimiterConfigs', + 'updates', + `must contain exactly one lane on a v1.5.0 pool, which sets rate limits one lane per transaction (got ${parsed.length}); split the batch into one call per lane`, + ) + const [update] = parsed + return callTx( + poolAddress, + iface.encodeFunctionData('setChainRateLimiterConfig', [ + update!.remoteChainSelector, + toTuple(update!.outbound), + toTuple(update!.inbound), + ]), + ) +} + +/** + * v1.5.1/v1.6.1: `setChainRateLimiterConfigs(uint64[], Config[] outbound, Config[] inbound)` — + * three parallel arrays, outbound before inbound. No `fastFinality` at these versions. + */ +const encodeBatchConfigs: Encoder = (iface, { poolAddress, updates }, version) => { + const parsed = parseUpdates('setChainRateLimiterConfigs', updates, false, version) + return callTx( + poolAddress, + iface.encodeFunctionData('setChainRateLimiterConfigs', [ + parsed.map((u) => u.remoteChainSelector), + parsed.map((u) => toTuple(u.outbound)), + parsed.map((u) => toTuple(u.inbound)), + ]), + ) +} + +/** + * v2.0.0: `setRateLimitConfig(RateLimitConfigArgs[])` — one struct per lane, folding the selector + * and the new `fastFinality` flag in with the two configs (outbound before inbound). + */ +const encodeRateLimitConfigV2_0_0: Encoder = (iface, { poolAddress, updates }, version) => { + const parsed = parseUpdates('setChainRateLimiterConfigs', updates, true, version) + return callTx( + poolAddress, + iface.encodeFunctionData('setRateLimitConfig', [ + parsed.map((u) => [ + u.remoteChainSelector, + u.fastFinality, + toTuple(u.outbound), + toTuple(u.inbound), + ]), + ]), + ) +} + +/** + * Sets the inbound/outbound rate limits of one or more configured lanes on a token pool, in a + * single transaction. Gated on the pool's `owner` **or** its `rateLimitAdmin`. + */ +export class SetChainRateLimiterConfigs extends EVMOperation { + readonly name = 'setChainRateLimiterConfigs' + + /** + * One entry per calldata shape: v1.5.0 has only the singular call, v1.6.1 inherits the v1.5.1 + * batch encoding, and v2.0.0 renamed and reshaped the call, hence its own entry. + */ + private readonly encoders: Partial> = { + [TokenPoolVersion.V1_5_0]: encodeSingleConfigV1_5_0, + [TokenPoolVersion.V1_5_1]: encodeBatchConfigs, + [TokenPoolVersion.V2_0_0]: encodeRateLimitConfigV2_0_0, + } + + /** + * Validates the pool address and every update before any RPC. `fastFinality` is *permitted* + * here, and the version-specific rate bounds are not applied (`null` version) — whether this + * pool has that field, and which bound its `RateLimiter` enforces, are only known once its + * version is resolved, so the version-specific encoder is what rejects those (see + * {@link parseUpdates}). + */ + protected override validate({ poolAddress, updates }: SetChainRateLimiterConfigsParams): void { + validateNonZeroAddress(this.name, 'poolAddress', poolAddress) + parseUpdates(this.name, updates, true, null) + } + + /** + * Reads the pool's type-and-version, floor-matches the encoder and its contract interface, then + * — when `sender` is known — pre-flights it against the pool's `owner` **or** its + * `rateLimitAdmin`. + * + * @remarks The role check lives here, not only in {@link execute}, so the offline / multisig + * path gets it too: `generateUnsignedSetChainRateLimiterConfigs` with an unauthorized `sender` + * would otherwise hand back a fully-formed transaction that reverts `Unauthorized` only after + * being reviewed and signed. Every sibling pool write gates in `buildUnsigned` for the same + * reason; this one is gated on a *disjunction* rather than the owner alone, so it reads both + * roles instead of using `assertPoolOwner`. + * @remarks Ordered *after* the encoder so a bad parameter fails on the one `typeAndVersion` + * probe rather than after two more role reads. + * @throws {@link CCTParamsInvalidError} if `sender` is neither the pool `owner` nor its (set) + * `rateLimitAdmin`, or a multi-lane `updates` is sent to a v1.5.0 pool + */ + protected async buildUnsigned( + chain: EVMChain, + params: SetChainRateLimiterConfigsParams, + ): Promise { + const { type, version } = await resolveTokenPool(chain, params.poolAddress) + const encode = resolveEncoder(this.encoders, version, this.name) + const unsigned = encode(getTokenPoolInterface(type, version), params, version) + if (params.sender !== undefined) + await this.#assertRateLimitRole(chain, params.poolAddress, params.sender, version) + return unsigned + } + + /** + * Rejects a `sender` that is neither the pool's `owner` nor its `rateLimitAdmin`. + * + * @remarks `rateLimitAdmin` is unset on most pools, where it reads as the zero address, so it is + * only compared once known to be *set*: an equality-first check would let a zero-address `sender` + * match an unset admin and authorize a transaction nobody can send. + * @remarks `version` selects which getter reports `rateLimitAdmin` (standalone pre-2.0.0, folded + * into `getDynamicConfig` at 2.0.0). Both roles are read directly, not via the + * `getTokenPoolState` query op — see {@link readTokenPoolOwner} for why. + * @throws {@link CCTParamsInvalidError} if `sender` holds neither role + */ + async #assertRateLimitRole( + chain: EVMChain, + poolAddress: string, + sender: string, + version: TokenPoolVersion, + ): Promise { + const [owner, rateLimitAdmin] = await Promise.all([ + readTokenPoolOwner(chain, poolAddress), + readTokenPoolRateLimitAdmin(chain, poolAddress, version), + ]) + + const signer = getAddress(sender) + // an unset rateLimitAdmin is the zero address; exclude it before comparing or a zero-address + // `sender` would match it + const isRateLimitAdmin = rateLimitAdmin !== ZeroAddress && rateLimitAdmin === signer + if (owner !== signer && !isRateLimitAdmin) { + throw new CCTParamsInvalidError( + this.name, + 'sender', + `must be the pool owner (${owner})${ + rateLimitAdmin === ZeroAddress + ? ' — this pool has no rateLimitAdmin set' + : ` or its rateLimitAdmin (${rateLimitAdmin})` + }`, + ) + } + } + + /** + * Signs and submits, binding `sender` to the signing wallet's address — see + * {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is rejected rather than + * signed. The owner-or-`rateLimitAdmin` gate is {@link buildUnsigned}'s. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if any param is invalid, or `sender` is neither the + * wallet's address, the pool `owner`, nor the pool's (set) `rateLimitAdmin`, or a multi-lane + * `updates` is sent to a v1.5.0 pool + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/set-dynamic-config.test.ts b/ccip-sdk/src/cct/evm/token-pool/operations/set-dynamic-config.test.ts new file mode 100644 index 000000000..ead339ca8 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/set-dynamic-config.test.ts @@ -0,0 +1,284 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { parseTypeAndVersion } from '../../../../utils.ts' +import { CCTOperationUnsupportedError, CCTParamsInvalidError } from '../../../errors.ts' +import { type TokenPoolFamily, TOKEN_POOL_INTERFACES, TokenPoolVersion } from '../contracts.ts' +import { type SetDynamicConfigParams, SetDynamicConfig } from './set-dynamic-config.ts' + +const POOL = '0x' + '11'.repeat(20) +const OWNER = '0x' + '22'.repeat(20) +const RATE_LIMIT_ADMIN = '0x' + '33'.repeat(20) +const FEE_ADMIN = '0x' + '44'.repeat(20) +const ROUTER = '0x' + '55'.repeat(20) +const NOT_THE_OWNER = '0x' + '88'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +/** + * Byte-parity oracle: a fresh Interface built from the signature literal, so the assertion is + * independent of the SDK's cached, ABI-derived interfaces. + */ +const IFACE = new Interface([ + 'function setDynamicConfig(address router, address rateLimitAdmin, address feeAdmin)', +]) +const dataFor = (router: string, rateLimitAdmin: string, feeAdmin: string) => + IFACE.encodeFunctionData('setDynamicConfig', [router, rateLimitAdmin, feeAdmin]) + +const DATA = dataFor(ROUTER, RATE_LIMIT_ADMIN, FEE_ADMIN) + +/** Pool type reported by `typeAndVersion` for each ABI family. */ +const POOL_TYPE: Record = { + BurnMint: 'BurnMintTokenPool', + LockRelease: 'LockReleaseTokenPool', +} + +/** + * EVMChain stub: `typeAndVersion` reports the requested family/version, and `provider.call` + * answers `owner()` — the only read this op makes. Any other selector reverts, which is what + * pins "no hidden `getDynamicConfig()` read". + */ +function stubChain({ + family = 'BurnMint', + version = TokenPoolVersion.V2_0_0, + owner = OWNER, + onCall, +}: { + family?: TokenPoolFamily + version?: TokenPoolVersion + owner?: string + onCall?: (selector?: string) => void +} = {}): EVMChain { + const iface = TOKEN_POOL_INTERFACES[family][version] + return { + provider: { + call: async ({ data }: { data: string }) => { + onCall?.(data.slice(0, 10)) + if (data.slice(0, 10) !== iface.getFunction('owner')!.selector) + throw makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: null, data }, + invocation: null, + revert: null, + }) + return iface.encodeFunctionResult('owner', [owner]) + }, + }, + logger: { debug() {}, info() {}, warn() {}, error() {} }, + typeAndVersion: () => { + onCall?.() + return Promise.resolve(parseTypeAndVersion(`${POOL_TYPE[family]} ${version}`)) + }, + nextNonce: async () => 0, + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +function fakeSigner(address = OWNER, waitError?: Error) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => (waitError ? Promise.reject(waitError) : Promise.resolve({ status: 1 })), + }), + } +} + +const op = new SetDynamicConfig() + +function generate(chain: EVMChain, overrides: Partial = {}) { + return op.generate(chain, { + poolAddress: POOL, + router: ROUTER, + rateLimitAdmin: RATE_LIMIT_ADMIN, + feeAdmin: FEE_ADMIN, + sender: OWNER, + ...overrides, + }) +} + +/** Versions with no `setDynamicConfig` — the struct write landed in 2.0.0. */ +const UNSUPPORTED = [ + TokenPoolVersion.V1_5_0, + TokenPoolVersion.V1_5_1, + TokenPoolVersion.V1_6_1, +] as const + +describe('SetDynamicConfig (cct/evm)', () => { + describe('generate', () => { + for (const family of ['BurnMint', 'LockRelease'] as const) { + it(`encodes setDynamicConfig(router, rateLimitAdmin, feeAdmin) for a ${family} 2.0.0 pool`, async () => { + const unsigned = await generate(stubChain({ family })) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, POOL) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, DATA) + }) + } + + it('emits identical calldata for both ABI families', async () => { + const [burnMint, lockRelease] = await Promise.all([ + generate(stubChain({ family: 'BurnMint' })), + generate(stubChain({ family: 'LockRelease' })), + ]) + assert.equal(burnMint.transactions[0]!.data, lockRelease.transactions[0]!.data) + }) + + it('allows the zero address to clear either delegate role', async () => { + const unsigned = await generate(stubChain(), { + rateLimitAdmin: ZeroAddress, + feeAdmin: ZeroAddress, + }) + assert.equal(unsigned.transactions[0]!.data, dataFor(ROUTER, ZeroAddress, ZeroAddress)) + }) + + it('omits from — and skips the owner read — when sender is not supplied', async () => { + let calls = 0 + const unsigned = await generate(stubChain({ onCall: () => (calls += 1) }), { + sender: undefined, + }) + assert.equal(unsigned.transactions[0]!.from, undefined) + // typeAndVersion only; no owner() round trip + assert.equal(calls, 1) + }) + + // the TOCTOU guard: all three fields come from the caller, so nothing is read back and + // baked into the calldata between build time and (possibly much later) signing. + it('never reads getDynamicConfig — the only call made is owner()', async () => { + const iface = TOKEN_POOL_INTERFACES.BurnMint[TokenPoolVersion.V2_0_0] + const selectors: (string | undefined)[] = [] + await generate(stubChain({ onCall: (selector) => selectors.push(selector) })) + assert.deepEqual(selectors, [undefined, iface.getFunction('owner')!.selector]) + assert.ok(!selectors.includes(iface.getFunction('getDynamicConfig')!.selector)) + }) + }) + + describe('validation', () => { + for (const [param, value] of [ + ['poolAddress', 'not-an-address'], + ['poolAddress', ZeroAddress], + ['router', 'not-an-address'], + ['router', ZeroAddress], + ['rateLimitAdmin', 'not-an-address'], + ['feeAdmin', 'not-an-address'], + ['sender', 'not-an-address'], + ] as const) { + it(`rejects ${param} = ${value} before any RPC`, async () => { + let called = false + await assert.rejects( + () => generate(stubChain({ onCall: () => (called = true) }), { [param]: value }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setDynamicConfig' && + err.context.param === param, + ) + assert.equal(called, false) + }) + } + }) + + describe('version dispatch', () => { + it('supports 2.0.0', async () => { + const unsigned = await generate(stubChain({ version: TokenPoolVersion.V2_0_0 })) + assert.equal(unsigned.transactions[0]!.data, DATA) + }) + + // no `null` ceiling is registered below 2.0.0: floor-match walks downwards and finds + // nothing at or below these versions, so they are unsupported for free. + for (const version of UNSUPPORTED) { + it(`rejects ${version} — setDynamicConfig landed in 2.0.0`, async () => { + await assert.rejects( + () => generate(stubChain({ version })), + (err: unknown) => + err instanceof CCTOperationUnsupportedError && + err.context.operation === 'setDynamicConfig' && + err.context.version === version, + ) + }) + } + + it('reports a pre-2.0.0 LockRelease pool unsupported too', async () => { + await assert.rejects( + () => generate(stubChain({ family: 'LockRelease', version: TokenPoolVersion.V1_6_1 })), + CCTOperationUnsupportedError, + ) + }) + }) + + describe('pre-transaction validation', () => { + it('rejects a sender that is not the pool owner', async () => { + await assert.rejects( + () => generate(stubChain({ owner: NOT_THE_OWNER })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setDynamicConfig' && + err.context.param === 'sender', + ) + }) + }) + + describe('execute', () => { + const params = { + poolAddress: POOL, + router: ROUTER, + rateLimitAdmin: RATE_LIMIT_ADMIN, + feeAdmin: FEE_ADMIN, + } + + it('signs and submits as the owner, resolving to the tx hash', async () => { + assert.deepEqual(await op.execute(stubChain(), { ...params, wallet: fakeSigner() }), { + hash: HASH, + }) + }) + + it('maps an on-chain revert to CCIPExecTxRevertedError', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + ...params, + wallet: fakeSigner(OWNER, makeError('execution reverted', 'CALL_EXCEPTION')), + }), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && err.context.operation === 'setDynamicConfig', + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { ...params, wallet: {} }), + CCIPWalletInvalidError, + ) + }) + + it('rejects a sender that is not the executing wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { ...params, sender: FEE_ADMIN, wallet: fakeSigner() }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects a wallet that is not the pool owner', async () => { + await assert.rejects( + () => op.execute(stubChain(), { ...params, wallet: fakeSigner(FEE_ADMIN) }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setDynamicConfig' && + err.context.param === 'sender' && + // names the owner it read, so the caller can see which address it needed + err.message.includes(OWNER), + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/set-dynamic-config.ts b/ccip-sdk/src/cct/evm/token-pool/operations/set-dynamic-config.ts new file mode 100644 index 000000000..b7393d42a --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/set-dynamic-config.ts @@ -0,0 +1,163 @@ +/** + * setDynamicConfig — writes a v2.0.0 TokenPool's whole dynamic config in one call: the `router` + * it accepts ramp calls from, plus the `rateLimitAdmin` and `feeAdmin` delegate roles. + * + * @remarks **v2.0.0 only.** This is where the standalone pre-2.0.0 setters went: `setRouter` and + * `setRateLimitAdmin` were removed and the three fields folded into one struct written together. + * On a 1.5.0/1.5.1/1.6.1 pool the encoder table matches nothing at or below the resolved version, + * so the op reports itself unsupported — use `setRateLimitAdmin` there. + * + * **This op does not read `getDynamicConfig()` to fill in fields the caller left out, and all + * three are therefore required.** `generateUnsignedSetDynamicConfig` has to produce deterministic + * calldata: a multisig or cold wallet may sign it days after it was built, and a hidden read at + * build time would open a TOCTOU window in which the "current" value baked into the calldata has + * since changed on-chain — silently reverting an unrelated config change made in the interim. + * Callers read the current triple with `getTokenPoolState` (which on a 2.0.0 pool already returns + * `router`, `rateLimitAdmin` and `feeAdmin`, sourced from `getDynamicConfig()`) and pass all three + * back explicitly, so what is signed is exactly what was reviewed. + * + * Owner-only, deliberately: the pool accepts rate-limit *config* writes from the current + * `rateLimitAdmin` as well as the owner, but this call assigns that role, so admitting the + * `rateLimitAdmin` as `sender` would let it reassign or entrench its own privilege. + * + * @packageDocumentation + */ + +import type { Interface } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { validateAddress, validateNonZeroAddress } from '../../validate.ts' +import { + TokenPoolVersion, + assertPoolOwner, + getTokenPoolInterface, + resolveEncoder, + resolveTokenPool, +} from '../contracts.ts' + +/** + * Parameters for {@link SetDynamicConfig}. The three config fields keep the contract struct's + * names — `router`/`rateLimitAdmin`/`feeAdmin`, with no `new` prefix — because this op replaces + * the whole struct rather than assigning one role, and every field is **required**: omitting one + * would mean reading the current value at build time, which is exactly what this op refuses to do + * (see the module remarks). + */ +export type SetDynamicConfigParams = { + /** Token pool to reconfigure. Must be non-zero — it is the tx `to`, and a call to `0x0` hits no + * code, so it would mine as a successful no-op. */ + poolAddress: string + /** + * Router the pool accepts `lockOrBurn`/`releaseOrMint` calls from. Must be non-zero: unlike the + * two admin roles this is not a delegable privilege but the pool's only bridging counterparty, + * so a zero value does not "clear" anything — it detaches the pool from CCIP entirely and every + * transfer through it reverts until an owner tx restores it. + */ + router: string + /** + * Address allowed to change the pool's rate limits alongside the owner. The zero address is + * **allowed** and meaningful: it clears the delegation, leaving the owner as the only account + * that can change rate limits. Revoking a delegated admin is legitimate — and on incident + * response, urgent — so it is not rejected here. + */ + rateLimitAdmin: string + /** + * Address allowed to change the pool's token-transfer fee config alongside the owner. Zero is + * **allowed**, on the same reasoning as {@link SetDynamicConfigParams.rateLimitAdmin}. + */ + feeAdmin: string + /** + * The pool owner. Sets `tx.from` for offline / multisig signing, and when supplied is checked + * against the pool's on-chain `owner()` before any calldata is built. Optional for + * {@link SetDynamicConfig.generate} (an offline builder may not yet know the signer); + * {@link SetDynamicConfig.execute} defaults it to the signing wallet, so the owner check always + * runs on a broadcast tx. + */ + sender?: string +} + +/** Encodes `setDynamicConfig` calldata against the resolved pool {@link Interface}. */ +type Encoder = (iface: Interface, params: SetDynamicConfigParams) => UnsignedEVMTx + +const encodeSetDynamicConfig: Encoder = ( + iface, + { poolAddress, router, rateLimitAdmin, feeAdmin }, +) => + callTx( + poolAddress, + iface.encodeFunctionData('setDynamicConfig', [router, rateLimitAdmin, feeAdmin]), + ) + +/** Replaces a v2.0.0 TokenPool's dynamic config (`router`, `rateLimitAdmin`, `feeAdmin`). */ +export class SetDynamicConfig extends EVMOperation { + readonly name = 'setDynamicConfig' + + /** + * v2.0.0 only, and no `null` ceiling is needed for the versions below it: floor-match walks + * *downwards* from the resolved version, so 1.5.0/1.5.1/1.6.1 find nothing at or below + * themselves and are reported unsupported for free. + */ + private readonly encoders: Partial> = { + [TokenPoolVersion.V2_0_0]: encodeSetDynamicConfig, + } + + /** Validates all four addresses before any RPC; only `router` and `poolAddress` must be non-zero. */ + protected override validate({ + poolAddress, + router, + rateLimitAdmin, + feeAdmin, + }: SetDynamicConfigParams): void { + validateNonZeroAddress(this.name, 'poolAddress', poolAddress) + validateNonZeroAddress(this.name, 'router', router) + validateAddress(this.name, 'rateLimitAdmin', rateLimitAdmin) + validateAddress(this.name, 'feeAdmin', feeAdmin) + } + + /** + * Resolves the pool's type/version, confirms `sender` (when given) is the pool owner, then + * floor-matches the encoder against that version. No `getDynamicConfig()` read — see the + * module remarks. + * @remarks The owner check lives here, not only in {@link execute}, so the offline / multisig + * path gets it too: `generateUnsignedSetDynamicConfig` with an unauthorized `sender` would + * otherwise hand back a fully-formed transaction that reverts `Unauthorized` only after being + * reviewed and signed. Every sibling owner-gated pool write gates in `buildUnsigned` for the + * same reason. + * @remarks Ordered *after* the encoder so a pre-2.0.0 pool reports the real problem (no such + * function) rather than spending a round trip and failing on an authorization detail. + * @throws {@link CCTOperationUnsupportedError} on a pre-v2.0.0 pool, which has no + * `setDynamicConfig` + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the pool owner + */ + protected async buildUnsigned( + chain: EVMChain, + params: SetDynamicConfigParams, + ): Promise { + const { type, version } = await resolveTokenPool(chain, params.poolAddress) + const encode = resolveEncoder(this.encoders, version, this.name) + const unsigned = encode(getTokenPoolInterface(type, version), params) + if (params.sender !== undefined) + await assertPoolOwner(this.name, chain, params.poolAddress, params.sender) + return unsigned + } + + /** + * Signs and submits as the pool owner, defaulting `sender` to the signing wallet — the only + * address that can satisfy {@link buildUnsigned}'s owner check for a broadcast tx. See + * {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is rejected rather + * than signed. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address, + * or is not the pool owner + * @throws {@link CCTOperationUnsupportedError} on a pre-v2.0.0 pool + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/set-rate-limit-admin.test.ts b/ccip-sdk/src/cct/evm/token-pool/operations/set-rate-limit-admin.test.ts new file mode 100644 index 000000000..62431fd52 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/set-rate-limit-admin.test.ts @@ -0,0 +1,254 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { parseTypeAndVersion } from '../../../../utils.ts' +import { CCTOperationUnsupportedError, CCTParamsInvalidError } from '../../../errors.ts' +import { type TokenPoolFamily, TOKEN_POOL_INTERFACES, TokenPoolVersion } from '../contracts.ts' +import { type SetRateLimitAdminParams, SetRateLimitAdmin } from './set-rate-limit-admin.ts' + +const POOL = '0x' + '11'.repeat(20) +const OWNER = '0x' + '22'.repeat(20) +const NEW_ADMIN = '0x' + '44'.repeat(20) +const NOT_THE_OWNER = '0x' + '88'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +/** + * Byte-parity oracle: a fresh Interface built from the signature literal, so the assertion is + * independent of the SDK's cached, ABI-derived interfaces. + */ +const IFACE = new Interface(['function setRateLimitAdmin(address rateLimitAdmin)']) +const dataFor = (admin: string) => IFACE.encodeFunctionData('setRateLimitAdmin', [admin]) + +/** Pool type reported by `typeAndVersion` for each ABI family. */ +const POOL_TYPE: Record = { + BurnMint: 'BurnMintTokenPool', + LockRelease: 'LockReleaseTokenPool', +} + +/** + * EVMChain stub: `typeAndVersion` reports the requested family/version, and `provider.call` + * answers `owner()` (the only read this op makes) off the pool's own Interface. Every other + * selector reverts, which is what pins "no other RPC". + */ +function stubChain({ + family = 'BurnMint', + version = TokenPoolVersion.V1_5_0, + owner = OWNER, + onCall, +}: { + family?: TokenPoolFamily + version?: TokenPoolVersion + owner?: string + onCall?: () => void +} = {}): EVMChain { + const iface = TOKEN_POOL_INTERFACES[family][version] + return { + provider: { + call: async ({ data }: { data: string }) => { + onCall?.() + if (data.slice(0, 10) !== iface.getFunction('owner')!.selector) + throw makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: null, data }, + invocation: null, + revert: null, + }) + return iface.encodeFunctionResult('owner', [owner]) + }, + }, + logger: { debug() {}, info() {}, warn() {}, error() {} }, + typeAndVersion: () => { + onCall?.() + return Promise.resolve(parseTypeAndVersion(`${POOL_TYPE[family]} ${version}`)) + }, + nextNonce: async () => 0, + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +function fakeSigner(address = OWNER, waitError?: Error) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => (waitError ? Promise.reject(waitError) : Promise.resolve({ status: 1 })), + }), + } +} + +const op = new SetRateLimitAdmin() + +function generate(chain: EVMChain, overrides: Partial = {}) { + return op.generate(chain, { + poolAddress: POOL, + newRateLimitAdmin: NEW_ADMIN, + sender: OWNER, + ...overrides, + }) +} + +/** Versions that still declare `setRateLimitAdmin`; 2.0.0 removed it. */ +const SUPPORTED = [ + TokenPoolVersion.V1_5_0, + TokenPoolVersion.V1_5_1, + TokenPoolVersion.V1_6_1, +] as const + +describe('SetRateLimitAdmin (cct/evm)', () => { + describe('generate', () => { + for (const version of SUPPORTED) { + for (const family of ['BurnMint', 'LockRelease'] as const) { + it(`encodes setRateLimitAdmin(admin) for a ${family} ${version} pool`, async () => { + const unsigned = await generate(stubChain({ family, version })) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, POOL) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, dataFor(NEW_ADMIN)) + }) + } + + it(`emits identical calldata for both ABI families at ${version}`, async () => { + const [burnMint, lockRelease] = await Promise.all([ + generate(stubChain({ family: 'BurnMint', version })), + generate(stubChain({ family: 'LockRelease', version })), + ]) + assert.equal(burnMint.transactions[0]!.data, lockRelease.transactions[0]!.data) + }) + } + + it('allows the zero address to clear the rate-limit admin role', async () => { + const unsigned = await generate(stubChain(), { newRateLimitAdmin: ZeroAddress }) + assert.equal(unsigned.transactions[0]!.data, dataFor(ZeroAddress)) + }) + + it('omits from — and skips the owner read — when sender is not supplied', async () => { + let calls = 0 + const unsigned = await generate(stubChain({ onCall: () => (calls += 1) }), { + sender: undefined, + }) + assert.equal(unsigned.transactions[0]!.from, undefined) + // typeAndVersion only; no owner() round trip + assert.equal(calls, 1) + }) + }) + + describe('validation', () => { + for (const [param, value] of [ + ['poolAddress', 'not-an-address'], + ['poolAddress', ZeroAddress], + ['newRateLimitAdmin', 'not-an-address'], + ['sender', 'not-an-address'], + ] as const) { + it(`rejects ${param} = ${value} before any RPC`, async () => { + let called = false + await assert.rejects( + () => generate(stubChain({ onCall: () => (called = true) }), { [param]: value }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setRateLimitAdmin' && + err.context.param === param, + ) + assert.equal(called, false) + }) + } + }) + + describe('version dispatch', () => { + for (const version of SUPPORTED) { + it(`supports ${version}`, async () => { + const unsigned = await generate(stubChain({ version })) + assert.equal(unsigned.transactions[0]!.data, dataFor(NEW_ADMIN)) + }) + } + + it('rejects a 2.0.0 pool — the selector was removed, use setDynamicConfig', async () => { + await assert.rejects( + () => generate(stubChain({ version: TokenPoolVersion.V2_0_0 })), + (err: unknown) => + err instanceof CCTOperationUnsupportedError && + err.context.operation === 'setRateLimitAdmin' && + err.context.version === TokenPoolVersion.V2_0_0, + ) + }) + + it('reports 2.0.0 unsupported for the LockRelease family too', async () => { + await assert.rejects( + () => generate(stubChain({ family: 'LockRelease', version: TokenPoolVersion.V2_0_0 })), + CCTOperationUnsupportedError, + ) + }) + }) + + describe('pre-transaction validation', () => { + it('rejects a sender that is not the pool owner', async () => { + await assert.rejects( + () => generate(stubChain({ owner: NOT_THE_OWNER })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setRateLimitAdmin' && + err.context.param === 'sender', + ) + }) + }) + + describe('execute', () => { + const params = { poolAddress: POOL, newRateLimitAdmin: NEW_ADMIN } + + it('signs and submits as the owner, resolving to the tx hash', async () => { + assert.deepEqual(await op.execute(stubChain(), { ...params, wallet: fakeSigner() }), { + hash: HASH, + }) + }) + + it('maps an on-chain revert to CCIPExecTxRevertedError', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + ...params, + wallet: fakeSigner(OWNER, makeError('execution reverted', 'CALL_EXCEPTION')), + }), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && err.context.operation === 'setRateLimitAdmin', + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { ...params, wallet: {} }), + CCIPWalletInvalidError, + ) + }) + + it('rejects a sender that is not the executing wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { ...params, sender: NEW_ADMIN, wallet: fakeSigner() }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects a wallet that is not the pool owner', async () => { + await assert.rejects( + () => op.execute(stubChain(), { ...params, wallet: fakeSigner(NEW_ADMIN) }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setRateLimitAdmin' && + err.context.param === 'sender' && + // names the owner it read, so the caller can see which address it needed + err.message.includes(OWNER), + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/set-rate-limit-admin.ts b/ccip-sdk/src/cct/evm/token-pool/operations/set-rate-limit-admin.ts new file mode 100644 index 000000000..dcc18bb3d --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/set-rate-limit-admin.ts @@ -0,0 +1,132 @@ +/** + * setRateLimitAdmin — assigns the TokenPool role allowed to change rate limits alongside the + * owner (v1.5.0–v1.6.1 only). + * + * @remarks **Removed in v2.0.0.** The standalone `setRateLimitAdmin(address)` selector does not + * exist on a 2.0.0 pool: the role was folded into a three-field dynamic config + * (`router`/`rateLimitAdmin`/`feeAdmin`) written in one shot by `setDynamicConfig`. The encoder + * table therefore pins an explicit `null` ceiling at 2.0.0 so a 2.0.0 pool is reported + * unsupported instead of floor-matching the 1.5.0 encoder and emitting calldata for a selector + * the pool does not implement. Use {@link SetDynamicConfig} there. + * + * Owner-only, deliberately: unlike the rate-limit *config* ops — which the pool accepts from + * either the owner or the current `rateLimitAdmin` — this op assigns the role itself, so + * accepting the `rateLimitAdmin` as `sender` would let it reassign (or entrench) its own + * privilege. Only the pool `owner` is allowed through. + * + * @packageDocumentation + */ + +import type { Interface } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { validateAddress, validateNonZeroAddress } from '../../validate.ts' +import { + TokenPoolVersion, + assertPoolOwner, + getTokenPoolInterface, + resolveEncoder, + resolveTokenPool, +} from '../contracts.ts' + +/** Parameters for {@link SetRateLimitAdmin}. */ +export type SetRateLimitAdminParams = { + /** Token pool whose rate-limit admin role is being assigned. Must be non-zero — it is the tx + * `to`, and a call to `0x0` hits no code, so it would mine as a successful no-op. */ + poolAddress: string + /** + * Address to grant the rate-limit admin role to. Named `newRateLimitAdmin` to match the Solana + * op's public field (`cct/solana/token-pool/operations/set-rate-limit-admin.ts`) rather than the + * ABI's bare `rateLimitAdmin`, so cross-family callers write one shape. + * + * The zero address is **allowed** and meaningful: it clears the role, leaving the owner as the + * only account that can change rate limits. Revoking a delegated admin is a legitimate — and + * on incident response, urgent — operation, so it is not rejected here. + */ + newRateLimitAdmin: string + /** + * The pool owner. Sets `tx.from` for offline / multisig signing, and when supplied is checked + * against the pool's on-chain `owner()` before any calldata is built. Optional for + * {@link SetRateLimitAdmin.generate} (an offline builder may not yet know the signer); + * {@link SetRateLimitAdmin.execute} defaults it to the signing wallet, so the owner check + * always runs on a broadcast tx. + */ + sender?: string +} + +/** Encodes `setRateLimitAdmin` calldata against the resolved pool {@link Interface}. */ +type Encoder = (iface: Interface, params: SetRateLimitAdminParams) => UnsignedEVMTx + +const encodeSetRateLimitAdmin: Encoder = (iface, { poolAddress, newRateLimitAdmin }) => + callTx(poolAddress, iface.encodeFunctionData('setRateLimitAdmin', [newRateLimitAdmin])) + +/** + * Assigns a TokenPool's rate-limit admin role (v1.5.0–v1.6.1). Owner-only; removed in v2.0.0 in + * favour of {@link SetDynamicConfig}. + */ +export class SetRateLimitAdmin extends EVMOperation { + readonly name = 'setRateLimitAdmin' + + /** + * One 1.5.0 entry covers 1.5.1 and 1.6.1 by floor-match (the encoding never changed), and the + * explicit `null` at 2.0.0 is load-bearing, not decoration: without it a 2.0.0 pool would + * floor-match the 1.5.0 encoder and produce calldata for a selector that version removed. + */ + private readonly encoders: Partial> = { + [TokenPoolVersion.V1_5_0]: encodeSetRateLimitAdmin, + [TokenPoolVersion.V2_0_0]: null, + } + + /** Validates both addresses before any RPC; a zero `newRateLimitAdmin` clears the role. */ + protected override validate({ poolAddress, newRateLimitAdmin }: SetRateLimitAdminParams): void { + validateNonZeroAddress(this.name, 'poolAddress', poolAddress) + validateAddress(this.name, 'newRateLimitAdmin', newRateLimitAdmin) + } + + /** + * Resolves the pool's type/version, confirms `sender` (when given) is the pool owner, then + * floor-matches the encoder against that version. + * @remarks The owner check lives here, not only in {@link execute}, so the offline / multisig + * path gets it too: `generateUnsignedSetRateLimitAdmin` with an unauthorized `sender` would + * otherwise hand back a fully-formed transaction that reverts `Unauthorized` only after being + * reviewed and signed. Every sibling owner-gated pool write gates in `buildUnsigned` for the + * same reason. + * @remarks Ordered *after* the encoder so a 2.0.0 pool reports the real problem (removed + * selector) rather than spending a round trip and failing on an authorization detail. + * @throws {@link CCTOperationUnsupportedError} on a v2.0.0 pool — the selector was removed; + * use {@link SetDynamicConfig} + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the pool owner + */ + protected async buildUnsigned( + chain: EVMChain, + params: SetRateLimitAdminParams, + ): Promise { + const { type, version } = await resolveTokenPool(chain, params.poolAddress) + const encode = resolveEncoder(this.encoders, version, this.name) + const unsigned = encode(getTokenPoolInterface(type, version), params) + if (params.sender !== undefined) + await assertPoolOwner(this.name, chain, params.poolAddress, params.sender) + return unsigned + } + + /** + * Signs and submits as the pool owner, defaulting `sender` to the signing wallet — the only + * address that can satisfy {@link buildUnsigned}'s owner check for a broadcast tx. See + * {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is rejected rather + * than signed. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address, + * or is not the pool owner + * @throws {@link CCTOperationUnsupportedError} on a v2.0.0 pool + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/set-remote-pool.test.ts b/ccip-sdk/src/cct/evm/token-pool/operations/set-remote-pool.test.ts new file mode 100644 index 000000000..55f18e1d9 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/set-remote-pool.test.ts @@ -0,0 +1,326 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError, toBeHex } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { parseTypeAndVersion } from '../../../../utils.ts' +import { CCTOperationUnsupportedError, CCTParamsInvalidError } from '../../../errors.ts' +import { + type TokenPoolType, + TOKEN_POOL_INTERFACES, + TokenPoolVersion, + getTokenPoolFamily, +} from '../contracts.ts' +import { type SetRemotePoolParams, SetRemotePool } from './set-remote-pool.ts' + +const POOL = '0x' + '11'.repeat(20) +const TOKEN = '0x' + '22'.repeat(20) +const ROUTER = '0x' + '33'.repeat(20) +const OWNER = '0x' + '44'.repeat(20) +const RMN_PROXY = '0x' + '55'.repeat(20) +const RATE_LIMIT_ADMIN = '0x' + '66'.repeat(20) +const LOCKBOX = '0x' + '77'.repeat(20) +const NOT_THE_OWNER = '0x' + '88'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +/** The remote pool this lane is being pointed at. */ +const REMOTE_POOL = '0x' + '99'.repeat(20) + +const SELECTOR = 5009297550715157269n // ethereum-mainnet +const SOLANA_SELECTOR = 16423721717087811551n // solana-devnet + +/** Calldata built from a fresh Interface — never the SDK's cached one, or this proves nothing. */ +const FRESH = new Interface([ + 'function setRemotePool(uint64 remoteChainSelector, bytes remotePoolAddress)', +]) +const expectedData = (remotePoolAddress = REMOTE_POOL, selector = SELECTOR) => + FRESH.encodeFunctionData('setRemotePool', [selector, remotePoolAddress]) + +/** The `getTokenPoolState` getters the owner gate reads, per version generation. */ +function poolReads(version: TokenPoolVersion, type: TokenPoolType, owner: string) { + if (version !== TokenPoolVersion.V2_0_0) + return { + getToken: [TOKEN], + owner: [owner], + getRouter: [ROUTER], + getRmnProxy: [RMN_PROXY], + getRateLimitAdmin: [RATE_LIMIT_ADMIN], + getSupportedChains: [[SELECTOR]], + } + return { + getToken: [TOKEN], + owner: [owner], + getRmnProxy: [RMN_PROXY], + getTokenDecimals: [18], + getSupportedChains: [[SELECTOR]], + getDynamicConfig: [ROUTER, RATE_LIMIT_ADMIN, RATE_LIMIT_ADMIN], + getAllowedFinalityConfig: [toBeHex(0, 4)], + ...(getTokenPoolFamily(type) === 'LockRelease' ? { getLockBox: [LOCKBOX] } : {}), + } +} + +type Calls = { typeAndVersion: number; remotes: number; calls: number } + +/** + * EVMChain stub: reports `type`/`version` and answers the owner-gate getters off the pool's own + * Interface. `getTokenPoolRemotes` is wired only to prove this op never calls it — a wholesale + * replace has no membership precondition to check. + */ +function stubChain({ + type = 'BurnMintTokenPool', + version = TokenPoolVersion.V1_5_0, + owner = OWNER, + seen = { typeAndVersion: 0, remotes: 0, calls: 0 }, +}: { + type?: TokenPoolType + version?: TokenPoolVersion + owner?: string + seen?: Calls +} = {}): EVMChain { + const iface = TOKEN_POOL_INTERFACES[getTokenPoolFamily(type)][version] + const responses = new Map( + Object.entries(poolReads(version, type, owner)).map(([fn, values]) => [ + iface.getFunction(fn)!.selector, + iface.encodeFunctionResult(fn, values), + ]), + ) + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + provider: { + call: ({ data }: { data: string }) => { + seen.calls++ + const encoded = responses.get(data.slice(0, 10)) + if (!encoded) + throw makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: null, data }, + invocation: null, + revert: null, + }) + return Promise.resolve(encoded) + }, + }, + typeAndVersion: () => { + seen.typeAndVersion++ + return Promise.resolve(parseTypeAndVersion(`${type} ${version}`)) + }, + getTokenInfo: () => Promise.resolve({ decimals: 18, symbol: 'TKN', name: 'Token' }), + getTokenPoolRemotes: () => { + seen.remotes++ + return Promise.resolve({}) + }, + nextNonce: () => Promise.resolve(0), + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +function fakeSigner(waitError?: Error, address = OWNER) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => (waitError ? Promise.reject(waitError) : Promise.resolve({ status: 1 })), + }), + } +} + +const op = new SetRemotePool() + +function generate(chain: EVMChain, overrides: Partial = {}) { + return op.generate(chain, { + poolAddress: POOL, + remoteChainSelector: SELECTOR, + remotePoolAddress: REMOTE_POOL, + sender: OWNER, + ...overrides, + }) +} + +/** The versions that dropped `setRemotePool` from the ABI. */ +const UNSUPPORTED = [TokenPoolVersion.V1_5_1, TokenPoolVersion.V1_6_1, TokenPoolVersion.V2_0_0] +const TYPES: TokenPoolType[] = ['BurnMintTokenPool', 'LockReleaseTokenPool'] + +describe('SetRemotePool (cct/evm)', () => { + describe('generate', () => { + for (const type of TYPES) { + it(`encodes setRemotePool(selector, bytes) for a ${type} 1.5.0`, async () => { + const unsigned = await generate(stubChain({ type })) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, POOL) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, expectedData()) + }) + } + + it('produces identical calldata for both ABI families', async () => { + const [burnMint, lockRelease] = await Promise.all( + TYPES.map(async (type) => (await generate(stubChain({ type }))).transactions[0]!.data), + ) + assert.equal(burnMint, lockRelease) + assert.equal(burnMint, expectedData()) + }) + + it('accepts a remotePoolAddress without the 0x prefix', async () => { + const unsigned = await generate(stubChain(), { + remotePoolAddress: REMOTE_POOL.slice(2).toUpperCase(), + }) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + }) + + it('encodes a 32-byte non-EVM remote pool address as-is', async () => { + const remotePoolAddress = '0x' + 'cd'.repeat(32) + const unsigned = await generate(stubChain(), { + remoteChainSelector: SOLANA_SELECTOR, + remotePoolAddress, + }) + assert.equal(unsigned.transactions[0]!.data, expectedData(remotePoolAddress, SOLANA_SELECTOR)) + }) + + it('omits from, and skips the owner read, when sender is not supplied', async () => { + const seen: Calls = { typeAndVersion: 0, remotes: 0, calls: 0 } + const unsigned = await generate(stubChain({ seen }), { sender: undefined }) + assert.equal(unsigned.transactions[0]!.from, undefined) + assert.equal(seen.calls, 0, 'no owner gate without a sender to compare') + }) + + it('never reads the lane: a wholesale replace has no membership precondition', async () => { + const seen: Calls = { typeAndVersion: 0, remotes: 0, calls: 0 } + await generate(stubChain({ seen })) + assert.equal(seen.remotes, 0) + }) + }) + + describe('validation', () => { + for (const [param, value] of [ + ['poolAddress', 'not-an-address'], + // a tx to `0x0` hits no code, so it mines as a successful no-op instead of reverting + ['poolAddress', ZeroAddress], + ['sender', 'not-an-address'], + ['remoteChainSelector', 1 as never], + ['remoteChainSelector', -1n], + ['remoteChainSelector', 2n ** 64n], + ['remotePoolAddress', ''], + ['remotePoolAddress', '0x'], + ['remotePoolAddress', '0xabc'], + ['remotePoolAddress', '0xzz'], + ['remotePoolAddress', 42 as never], + ] as const) { + it(`rejects ${param} = ${String(value)} before any RPC`, async () => { + const seen: Calls = { typeAndVersion: 0, remotes: 0, calls: 0 } + await assert.rejects( + () => generate(stubChain({ seen }), { [param]: value }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setRemotePool' && + err.context.param === param, + ) + assert.deepEqual([seen.typeAndVersion, seen.remotes, seen.calls], [0, 0, 0]) + }) + } + }) + + describe('version dispatch', () => { + it('encodes on v1.5.0, the only version that declares setRemotePool', async () => { + const unsigned = await generate(stubChain({ version: TokenPoolVersion.V1_5_0 })) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + }) + + for (const version of UNSUPPORTED) { + it(`is unsupported on v${version}, which dropped the function`, async () => { + // the null ceiling at v1.5.1 is what stops the floor-match from inheriting the v1.5.0 + // encoder here and emitting calldata for a selector these pools do not implement + const seen: Calls = { typeAndVersion: 0, remotes: 0, calls: 0 } + await assert.rejects( + () => generate(stubChain({ version, seen })), + (err: unknown) => + err instanceof CCTOperationUnsupportedError && + err.context.operation === 'setRemotePool' && + err.context.version === version, + ) + assert.deepEqual([seen.typeAndVersion, seen.remotes, seen.calls], [1, 0, 0]) + }) + } + + it('covers every known pool version', () => { + assert.deepEqual(Object.values(TokenPoolVersion), [TokenPoolVersion.V1_5_0, ...UNSUPPORTED]) + }) + + it('has no setRemotePool in any post-1.5.0 vendored ABI', () => { + for (const version of UNSUPPORTED) { + for (const family of ['BurnMint', 'LockRelease'] as const) { + assert.equal( + TOKEN_POOL_INTERFACES[family][version].getFunction('setRemotePool'), + null, + `${family} ${version}`, + ) + } + } + }) + }) + + describe('pre-transaction validation', () => { + it('rejects a sender that is not the pool owner', async () => { + await assert.rejects( + () => generate(stubChain({ owner: NOT_THE_OWNER })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setRemotePool' && + err.context.param === 'sender', + ) + }) + }) + + describe('execute', () => { + const params = { + poolAddress: POOL, + remoteChainSelector: SELECTOR, + remotePoolAddress: REMOTE_POOL, + } + + it('signs and submits as the owner, resolving to the tx hash', async () => { + assert.deepEqual(await op.execute(stubChain(), { ...params, wallet: fakeSigner() }), { + hash: HASH, + }) + }) + + it('maps an on-chain revert to CCIPExecTxRevertedError', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + ...params, + wallet: fakeSigner(makeError('execution reverted', 'CALL_EXCEPTION')), + }), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && err.context.operation === 'setRemotePool', + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { ...params, wallet: {} }), + CCIPWalletInvalidError, + ) + }) + + it('rejects a sender that is not the signing wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { ...params, sender: NOT_THE_OWNER, wallet: fakeSigner() }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setRemotePool' && + err.context.param === 'sender', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/set-remote-pool.ts b/ccip-sdk/src/cct/evm/token-pool/operations/set-remote-pool.ts new file mode 100644 index 000000000..1c294f0bb --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/set-remote-pool.ts @@ -0,0 +1,112 @@ +/** + * setRemotePool: replaces the remote pool address a v1.5.0 pool accepts on one lane. + * + * @remarks **v1.5.0 only.** A 1.5.0 pool holds exactly one remote pool per lane and this call + * overwrites it wholesale; v1.5.1 replaced it with the additive `addRemotePool` / + * `removeRemotePool` pair (a lane may hold several remote pools there), and no version from + * v1.5.1 up declares `setRemotePool` at all. Emulating it on a newer pool is deliberately not + * attempted — "replace" over a set of unknown size is not a single transaction — so this op + * reports itself unsupported there instead of guessing. + * + * @packageDocumentation + */ + +import type { Interface } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { + TokenPoolVersion, + assertPoolOwner, + getTokenPoolInterface, + resolveEncoder, + resolveTokenPool, +} from '../contracts.ts' +import { + type ParsedRemotePoolParams, + type RemotePoolParams, + parseRemotePoolParams, +} from '../remote-pool.ts' + +/** + * Parameters for {@link SetRemotePool} — see {@link RemotePoolParams}; `remotePoolAddress` is the + * remote chain's pool address as hex bytes, which becomes the lane's *only* remote pool. + */ +export type SetRemotePoolParams = RemotePoolParams + +/** {@link SetRemotePoolParams} as {@link SetRemotePool.parse} leaves it. */ +type ParsedSetRemotePoolParams = ParsedRemotePoolParams + +/** Encodes `setRemotePool` calldata against the resolved pool {@link Interface}. */ +type Encoder = (iface: Interface, params: ParsedSetRemotePoolParams) => UnsignedEVMTx + +const encodeSetRemotePool: Encoder = ( + iface, + { poolAddress, remoteChainSelector, remotePoolAddress }, +) => + callTx( + poolAddress, + iface.encodeFunctionData('setRemotePool', [remoteChainSelector, remotePoolAddress]), + ) + +/** Replaces a v1.5.0 pool's remote pool for one lane via `setRemotePool`. */ +export class SetRemotePool extends EVMOperation { + readonly name = 'setRemotePool' + + /** + * v1.5.0 only. The explicit `null` at v1.5.1 is load-bearing: it is the removal ceiling + * {@link resolveEncoder} stops its floor-match walk at, so v1.5.1/v1.6.1/v2.0.0 report the op + * as unsupported. Without it they would inherit the v1.5.0 encoder and emit calldata for a + * function selector those pools do not implement. + */ + private readonly encoders: Partial> = { + [TokenPoolVersion.V1_5_0]: encodeSetRemotePool, + [TokenPoolVersion.V1_5_1]: null, + } + + /** + * Validates the pool address, lane selector and remote pool bytes before any RPC, keeping the + * parsed `remotePoolAddress` so {@link buildUnsigned} encodes it without re-parsing. + */ + protected override parse(params: SetRemotePoolParams): ParsedSetRemotePoolParams { + return parseRemotePoolParams(this.name, params) + } + + /** + * Resolves the pool's version (rejecting anything past v1.5.0), confirms `sender` owns the + * pool, then encodes the call. No membership precondition: this call replaces whatever the lane + * held, so there is nothing to check it against. + * @throws {@link CCTOperationUnsupportedError} if the pool is v1.5.1 or newer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the pool owner + */ + protected async buildUnsigned( + chain: EVMChain, + params: ParsedSetRemotePoolParams, + ): Promise { + const { type, version } = await resolveTokenPool(chain, params.poolAddress) + // resolved before any further RPC, so an unsupported version fails on one call + const encode = resolveEncoder(this.encoders, version, this.name) + // owner-gated on-chain; surface it as a param error here instead of an on-chain revert + if (params.sender !== undefined) + await assertPoolOwner(this.name, chain, params.poolAddress, params.sender) + return encode(getTokenPoolInterface(type, version), params) + } + + /** + * Signs and submits as the pool owner, defaulting `sender` to the signing wallet — the only + * address that can satisfy {@link buildUnsigned}'s owner check for a broadcast tx. See + * {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is rejected. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address, or + * if any other param is invalid (see {@link buildUnsigned}) + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token-pool/operations/transfer-ownership.ts b/ccip-sdk/src/cct/evm/token-pool/operations/transfer-ownership.ts new file mode 100644 index 000000000..fbd0350b0 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/operations/transfer-ownership.ts @@ -0,0 +1,63 @@ +/** + * transferOwnership: proposes a new TokenPool owner (Ownable2Step; the new + * owner must later call acceptOwnership). + * + * @packageDocumentation + */ + +import type { Interface } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { EVMOperation, callTx } from '../../operation.ts' +import { validateAddress, validateNonZeroAddress } from '../../validate.ts' +import { + TokenPoolVersion, + getTokenPoolInterface, + resolveEncoder, + resolveTokenPool, +} from '../contracts.ts' + +/** Parameters for {@link TransferOwnership}. */ +export interface TransferOwnershipParams { + poolAddress: string + newOwner: string + /** Current pool owner; sets `tx.from` for offline / multisig signing. */ + sender?: string +} + +/** Encodes `transferOwnership` calldata against the resolved pool {@link Interface}. */ +type Encoder = (iface: Interface, params: TransferOwnershipParams) => UnsignedEVMTx + +const encodeTransferOwnership: Encoder = (iface, { newOwner, poolAddress }) => + callTx(poolAddress, iface.encodeFunctionData('transferOwnership', [newOwner])) + +/** Proposes a new TokenPool owner via Ownable2Step `transferOwnership`. */ +export class TransferOwnership extends EVMOperation { + readonly name = 'transferOwnership' + + /** + * Stable across pool versions: one V1_5_0 entry covers all via floor-match. + * Add another only when a version's encoding diverges. + */ + private readonly encoders: Partial> = { + [TokenPoolVersion.V1_5_0]: encodeTransferOwnership, + } + + /** Validates the pool and new-owner addresses before any RPC. */ + protected override validate({ poolAddress, newOwner }: TransferOwnershipParams): void { + validateNonZeroAddress(this.name, 'poolAddress', poolAddress) + validateAddress(this.name, 'newOwner', newOwner) + } + + /** Reads the pool's type-and-version, then floor-matches the encoder and its contract interface. */ + protected async buildUnsigned( + chain: EVMChain, + { poolAddress, newOwner }: TransferOwnershipParams, + ): Promise { + const { type, version } = await resolveTokenPool(chain, poolAddress) + const iface = getTokenPoolInterface(type, version) + const encode = resolveEncoder(this.encoders, version, this.name) + return encode(iface, { poolAddress, newOwner }) + } +} diff --git a/ccip-sdk/src/cct/evm/token-pool/rate-limit.test.ts b/ccip-sdk/src/cct/evm/token-pool/rate-limit.test.ts new file mode 100644 index 000000000..92d87fe35 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/rate-limit.test.ts @@ -0,0 +1,240 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { CCTParamsInvalidError } from '../../errors.ts' +import { TokenPoolVersion } from './contracts.ts' +import { parseRateLimitConfig } from './rate-limit.ts' + +describe('parseRateLimitConfig', () => { + const UINT128_MAX = 2n ** 128n - 1n + + it('returns an enabled config unchanged', () => { + assert.deepEqual( + parseRateLimitConfig( + 'op', + 'inboundRateLimiterConfig', + { enabled: true, capacity: 100n, rate: 10n }, + null, + ), + { enabled: true, capacity: 100n, rate: 10n }, + ) + }) + + it('allows rate to equal capacity when enabled, where the version permits it', () => { + // `null` (version not yet resolved) and the two relaxed versions; the strict ones are + // covered in `version-specific enabled-bucket bounds` below + for (const version of [null, TokenPoolVersion.V1_6_1, TokenPoolVersion.V2_0_0] as const) { + assert.deepEqual( + parseRateLimitConfig( + 'op', + 'inboundRateLimiterConfig', + { enabled: true, capacity: 10n, rate: 10n }, + version, + ), + { enabled: true, capacity: 10n, rate: 10n }, + `version ${String(version)}`, + ) + } + }) + + it('accepts uint128 max for both amounts', () => { + assert.deepEqual( + parseRateLimitConfig( + 'op', + 'inboundRateLimiterConfig', + { enabled: true, capacity: UINT128_MAX, rate: UINT128_MAX }, + null, + ), + { enabled: true, capacity: UINT128_MAX, rate: UINT128_MAX }, + ) + }) + + it('defaults omitted amounts to zero when disabled', () => { + assert.deepEqual( + parseRateLimitConfig('op', 'outboundRateLimiterConfig', { enabled: false }, null), + { enabled: false, capacity: 0n, rate: 0n }, + ) + }) + + it('accepts explicit zeros when disabled', () => { + assert.deepEqual( + parseRateLimitConfig( + 'op', + 'outboundRateLimiterConfig', + { enabled: false, capacity: 0n, rate: 0n }, + null, + ), + { enabled: false, capacity: 0n, rate: 0n }, + ) + }) + + it('reports failures with dotted param paths under the direction', () => { + const cases: Array<[unknown, string]> = [ + [undefined, 'inboundRateLimiterConfig'], + [null, 'inboundRateLimiterConfig'], + ['enabled', 'inboundRateLimiterConfig'], + [{}, 'inboundRateLimiterConfig.enabled'], + [{ enabled: 'yes' }, 'inboundRateLimiterConfig.enabled'], + // enabled with a missing amount: no defaulting applies, so the bound check reports it + [{ enabled: true, rate: 1n }, 'inboundRateLimiterConfig.capacity'], + [{ enabled: true, capacity: 1n }, 'inboundRateLimiterConfig.rate'], + [{ enabled: true, capacity: 1, rate: 1n }, 'inboundRateLimiterConfig.capacity'], + [{ enabled: true, capacity: -1n, rate: 0n }, 'inboundRateLimiterConfig.capacity'], + [ + { enabled: true, capacity: UINT128_MAX + 1n, rate: 0n }, + 'inboundRateLimiterConfig.capacity', + ], + [{ enabled: true, capacity: 0n, rate: -1n }, 'inboundRateLimiterConfig.rate'], + // rate above capacity is only an error while enabled + [{ enabled: true, capacity: 10n, rate: 11n }, 'inboundRateLimiterConfig.rate'], + // disabled must be all-zero, and the whole direction is blamed + [{ enabled: false, capacity: 1n }, 'inboundRateLimiterConfig'], + [{ enabled: false, rate: 1n }, 'inboundRateLimiterConfig'], + ] + + for (const [config, param] of cases) { + assert.throws( + () => parseRateLimitConfig('op', 'inboundRateLimiterConfig', config, null), + (error: unknown) => + error instanceof CCTParamsInvalidError && + error.context.operation === 'op' && + error.context.param === param, + `expected ${JSON.stringify(String(param))} for ${String(JSON.stringify(config, (_k, v) => (typeof v === 'bigint' ? String(v) : v)))}`, + ) + } + }) + + /** + * The enabled-bucket bound is version-dependent, and getting this wrong in either direction is a + * bug: + * + * - v1.5.0/v1.5.1 `RateLimiter._validateTokenBucketConfig` reverts `InvalidRateLimitRate` when + * `config.rate >= config.capacity || config.rate == 0`, so an enabled config needs + * `0 < rate < capacity` — calldata that violates it always reverts, and must fail locally. + * - v1.6.1/v2.0.0 relaxed that to `config.rate > config.capacity`, so `rate === capacity` and + * `rate === 0n` are *legitimate* there. Tightening the rule globally would be a new bug, which + * is what the accept-side cases below pin. + */ + describe('version-specific enabled-bucket bounds', () => { + const STRICT = [TokenPoolVersion.V1_5_0, TokenPoolVersion.V1_5_1] as const + const RELAXED = [TokenPoolVersion.V1_6_1, TokenPoolVersion.V2_0_0] as const + + for (const version of STRICT) { + it(`rejects rate === capacity when enabled on v${version}`, () => { + assert.throws( + () => + parseRateLimitConfig( + 'op', + 'outboundRateLimiterConfig', + { enabled: true, capacity: 10n, rate: 10n }, + version, + ), + (error: unknown) => + error instanceof CCTParamsInvalidError && + error.context.operation === 'op' && + error.context.param === 'outboundRateLimiterConfig.rate', + ) + }) + + it(`rejects a zero rate when enabled on v${version}`, () => { + assert.throws( + () => + parseRateLimitConfig( + 'op', + 'inboundRateLimiterConfig', + { enabled: true, capacity: 10n, rate: 0n }, + version, + ), + (error: unknown) => + error instanceof CCTParamsInvalidError && + error.context.operation === 'op' && + error.context.param === 'inboundRateLimiterConfig.rate', + ) + }) + + it(`still accepts 0 < rate < capacity on v${version}`, () => { + assert.deepEqual( + parseRateLimitConfig( + 'op', + 'inboundRateLimiterConfig', + { enabled: true, capacity: 10n, rate: 9n }, + version, + ), + { enabled: true, capacity: 10n, rate: 9n }, + ) + }) + + /** + * The version-independent `rate > capacity` bound must survive the strict tightening. If a + * refactor ever made the strict branch *replace* the base check rather than follow it, + * `rate > capacity` would become accepted on exactly the two versions that revert hardest + * on it — so this asserts both that it still throws and that it is the *base* message doing + * the throwing, which is what proves the order. + */ + it(`still rejects rate > capacity when enabled on v${version}`, () => { + assert.throws( + () => + parseRateLimitConfig( + 'op', + 'outboundRateLimiterConfig', + { enabled: true, capacity: 10n, rate: 11n }, + version, + ), + (error: unknown) => + error instanceof CCTParamsInvalidError && + error.context.operation === 'op' && + error.context.param === 'outboundRateLimiterConfig.rate' && + error.message.includes('must not exceed capacity when enabled'), + ) + }) + + it(`does not apply the strict rule to a disabled config on v${version}`, () => { + assert.deepEqual( + parseRateLimitConfig('op', 'inboundRateLimiterConfig', { enabled: false }, version), + { enabled: false, capacity: 0n, rate: 0n }, + ) + }) + } + + for (const version of RELAXED) { + it(`accepts rate === capacity when enabled on v${version}`, () => { + assert.deepEqual( + parseRateLimitConfig( + 'op', + 'outboundRateLimiterConfig', + { enabled: true, capacity: 10n, rate: 10n }, + version, + ), + { enabled: true, capacity: 10n, rate: 10n }, + ) + }) + + it(`accepts a zero rate when enabled on v${version}`, () => { + assert.deepEqual( + parseRateLimitConfig( + 'op', + 'inboundRateLimiterConfig', + { enabled: true, capacity: 10n, rate: 0n }, + version, + ), + { enabled: true, capacity: 10n, rate: 0n }, + ) + }) + + it(`still rejects rate > capacity when enabled on v${version}`, () => { + assert.throws( + () => + parseRateLimitConfig( + 'op', + 'inboundRateLimiterConfig', + { enabled: true, capacity: 10n, rate: 11n }, + version, + ), + (error: unknown) => + error instanceof CCTParamsInvalidError && + error.context.param === 'inboundRateLimiterConfig.rate', + ) + }) + } + }) +}) diff --git a/ccip-sdk/src/cct/evm/token-pool/rate-limit.ts b/ccip-sdk/src/cct/evm/token-pool/rate-limit.ts new file mode 100644 index 000000000..c7a8f689f --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/rate-limit.ts @@ -0,0 +1,162 @@ +/** + * The write-side rate-limit shape every EVM lane-config op shares, and its validation. + * + * @remarks Pulled out of `contracts.ts` (which is pool *contract metadata* — type/version + * resolution, cached interfaces, deploy artifacts) and out of the individual ops, so the + * caller-facing {@link RateLimitConfig} shape and the version-conditional enabled-bucket bound + * have exactly one definition. `applyChainUpdates` and `setChainRateLimiterConfigs` both build on + * this; each keeps its own parsed output shape. + * + * @packageDocumentation + */ + +import { CCTParamsInvalidError } from '../../errors.ts' +import { parseRecord, validateBoolean, validateUint128 } from '../validate.ts' +import { TokenPoolVersion } from './contracts.ts' + +/** + * Configuration for one direction of a token pool rate limiter, as CCT callers write it. + * + * @remarks Field-for-field identical to the Solana `RateLimitConfig` in + * `cct/solana/token-pool/operations/set-chain-rate-limit.ts`, so cross-family callers write one + * shape; only the bound differs (EVM `uint128` here, Solana `u64` there). + * + * The discriminant is spelled **`enabled`**, not the ABI's `isEnabled`: matching the Solana op's + * public field name matters more than matching the ABI, because callers write cross-family code + * against the SDK. Ops map `enabled` → `isEnabled` when building the on-chain + * `RateLimiter.Config` tuple. + * + * Distinct from the read-side `RateLimiterState` in `chain.ts`, which additionally carries + * the live `tokens` bucket balance — that is what a pool *reports*, this is what a caller *sets*. + * + * For a token with 6 decimals, pass `1_000_000n` to represent one token. + */ +export type RateLimitConfig = + | { + /** Whether this directional rate limit is enforced. */ + enabled: true + /** + * Maximum token amount in the bucket (`uint128`). Must be at least `rate` on v1.6.1/v2.0.0 + * pools, and strictly greater than `rate` on v1.5.0/v1.5.1 — see {@link RateLimitConfig.rate}. + */ + capacity: bigint + /** + * Token amount restored to the bucket per second (`uint128`). + * + * The bound the contracts enforce is **version-dependent**, so this is checked against the + * resolved pool version rather than one global rule: + * - **v1.6.1 / v2.0.0** — `rate <= capacity`. `rate === capacity` and `rate === 0n` are both + * legal (`RateLimiter._validateTokenBucketConfig` only reverts `InvalidRateLimitRate` when + * `rate > capacity`). + * - **v1.5.0 / v1.5.1** — stricter: `0n < rate < capacity`. Those versions revert when + * `rate >= capacity || rate == 0`, so a config that is fine on a newer pool is rejected here. + */ + rate: bigint + } + | { + /** Whether this directional rate limit is enforced. */ + enabled: false + /** Must be zero when provided; defaults to zero. */ + capacity?: bigint + /** Must be zero when provided; defaults to zero. */ + rate?: bigint + } + +/** + * A {@link RateLimitConfig} with its optional amounts resolved to concrete `bigint`s, still keyed + * `enabled`. Ops that encode the ABI's `isEnabled` re-key at the tuple boundary. + */ +export type ParsedRateLimitConfig = { + enabled: boolean + capacity: bigint + rate: bigint +} + +/** + * The versions whose `RateLimiter._validateTokenBucketConfig` rejects an enabled bucket unless + * `0 < rate < capacity`: + * + * ```solidity + * if (config.isEnabled) { if (config.rate >= config.capacity || config.rate == 0) revert InvalidRateLimitRate(config); } + * ``` + * + * v1.6.1 and v2.0.0 relaxed that to `if (config.rate > config.capacity) revert ...`, so on those + * versions `rate === capacity` and `rate === 0n` are legitimate and must NOT be rejected. + */ +const STRICT_RATE_BOUND_VERSIONS: readonly TokenPoolVersion[] = [ + TokenPoolVersion.V1_5_0, + TokenPoolVersion.V1_5_1, +] + +/** + * Validates one direction of a rate limiter and fills in its omitted amounts, mirroring the Solana + * `parseRateLimitConfig` with `uint128` bounds. + * + * @remarks `version` is required-and-nullable (not optional) so each call site states explicitly + * whether the version-specific bound applies: pre-RPC `validate()` passes `null` (version-independent + * checks only, so bad params still fail before the first `eth_call`), while version-specific + * encoders pass the resolved {@link TokenPoolVersion} and get the tightening. The bound changed + * between pool generations — see {@link STRICT_RATE_BOUND_VERSIONS}. + * @param operation - Operation name, for the error context. + * @param direction - Param path of this direction (e.g. `inboundRateLimiterConfig`); nested + * failures report as `${direction}.capacity` / `${direction}.rate` / `${direction}.enabled`. + * @param config - The caller-supplied value, unvalidated. + * @param version - Resolved pool version, or `null` when it is not known yet (pre-RPC validation), + * which applies the version-independent checks alone. + * @returns The direction with `capacity`/`rate` defaulted to `0n` when disabled and omitted. + * @throws {@link CCTParamsInvalidError} if `config` is not an object, `enabled` is not a boolean, + * either amount is not a `uint128`, `rate` exceeds `capacity` while enabled, `rate` is zero or + * equal to `capacity` while enabled on a v1.5.0/v1.5.1 pool, or either amount is non-zero while + * disabled + */ +export function parseRateLimitConfig( + operation: string, + direction: string, + config: unknown, + version: TokenPoolVersion | null, +): ParsedRateLimitConfig { + const input = parseRecord(operation, direction, config, 'rate-limit configuration') + const { enabled } = input + validateBoolean(operation, `${direction}.enabled`, enabled) + + // Only a disabled direction defaults: an enabled one must state both amounts, so an omitted + // amount stays `undefined` and is rejected by the uint128 check below, under its own path. + const capacity = !enabled && input.capacity === undefined ? 0n : input.capacity + const rate = !enabled && input.rate === undefined ? 0n : input.rate + validateUint128(operation, `${direction}.capacity`, capacity) + validateUint128(operation, `${direction}.rate`, rate) + + if (enabled && rate > capacity) { + throw new CCTParamsInvalidError( + operation, + `${direction}.rate`, + 'must not exceed capacity when enabled', + ) + } + // Version-specific tightening, applied ONLY where the contract itself is stricter: v1.5.0 and + // v1.5.1 revert `InvalidRateLimitRate` for an enabled bucket unless `0 < rate < capacity`. + if (enabled && version !== null && STRICT_RATE_BOUND_VERSIONS.includes(version)) { + if (rate === 0n) { + throw new CCTParamsInvalidError( + operation, + `${direction}.rate`, + `must be greater than zero when enabled on a v${version} pool, which reverts InvalidRateLimitRate on a zero rate`, + ) + } + if (rate === capacity) { + throw new CCTParamsInvalidError( + operation, + `${direction}.rate`, + `must be strictly less than capacity when enabled on a v${version} pool, which reverts InvalidRateLimitRate on rate == capacity (v1.6.1 and later allow it)`, + ) + } + } + if (!enabled && (capacity !== 0n || rate !== 0n)) { + throw new CCTParamsInvalidError( + operation, + direction, + 'must have zero capacity and rate when disabled', + ) + } + return { enabled, capacity, rate } +} diff --git a/ccip-sdk/src/cct/evm/token-pool/remote-pool.ts b/ccip-sdk/src/cct/evm/token-pool/remote-pool.ts new file mode 100644 index 000000000..b5767d193 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token-pool/remote-pool.ts @@ -0,0 +1,145 @@ +/** + * Shared internals of the three remote-pool write ops — `setRemotePool` (v1.5.0), + * `addRemotePool` and `removeRemotePool` (v1.5.1+): the parameter shape they have in common, + * `remotePoolAddress` parsing, and the per-lane membership read the add/remove preconditions + * are checked against. The owner gate itself is `assertPoolOwner` in `../contracts.ts`, + * shared with every other owner-gated pool write. + * + * @packageDocumentation + */ + +import { isHexString } from 'ethers' + +import { CCIPTokenPoolChainConfigNotFoundError } from '../../../errors/index.ts' +import type { EVMChain } from '../../../evm/index.ts' +import { networkInfo } from '../../../networks.ts' +import { decodeAddress } from '../../../utils.ts' +import { parseHexBytes, validateNonZeroAddress, validateUint64 } from '../validate.ts' + +/** + * Parameters shared by every remote-pool write op: which pool, which lane, and which remote pool. + * + * @remarks `remotePoolAddress` is the *remote* chain's pool address as raw bytes, not an EVM + * address: the lane's other end may be Solana, Aptos or Sui, whose addresses are 32 bytes. The + * contracts take it as `bytes` for exactly that reason, so it is accepted here as hex of any + * (even-digit) length rather than validated as an EVM address. + */ +export type RemotePoolParams = { + /** Local token pool contract being reconfigured. */ + poolAddress: string + /** CCIP selector of the lane's remote chain (`uint64`). */ + remoteChainSelector: bigint + /** + * Remote chain's pool address as hex bytes; the `0x` prefix is optional. Any even number of + * hex digits is accepted — a non-EVM remote's address is not 20 bytes. + */ + remotePoolAddress: string + /** Current pool owner; sets `tx.from` for offline / multisig signing. */ + sender?: string +} + +/** + * {@link RemotePoolParams} as {@link parseRemotePoolParams} leaves it: `remotePoolAddress` + * normalised to 0x-prefixed lowercase hex, so `buildUnsigned` encodes it without re-parsing. + */ +export type ParsedRemotePoolParams = RemotePoolParams & { remotePoolAddress: string } + +/** + * Normalises `remotePoolAddress` to 0x-prefixed lowercase hex, the form `bytes` calldata is + * encoded from. + * @remarks Deliberately not an address check: see {@link RemotePoolParams.remotePoolAddress}. + * Only the encoding is constrained — hex digits, `0x` optional, whole bytes, non-empty. A thin + * alias over the shared {@link parseHexBytes} that fixes the param path these three ops all + * blame; the parser itself lives in `../validate.ts` alongside its sibling validators, shared + * with `applyChainUpdates`, which validates the same kind of value. + * @throws {@link CCTParamsInvalidError} if `value` is not a non-empty, whole-byte hex string + */ +export function parseRemotePoolAddress(operation: string, value: unknown): string { + return parseHexBytes(operation, 'remotePoolAddress', value) +} + +/** + * Validates the params every remote-pool op takes, before any RPC. + * @remarks `poolAddress` is required to be **non-zero**, not merely well formed: a call to `0x0` + * hits no code, so it would mine as a *successful* no-op rather than failing. + * @returns The parsed `remotePoolAddress` (0x-prefixed lowercase hex). + * @throws {@link CCTParamsInvalidError} if `poolAddress` is not a valid non-zero address, + * `remoteChainSelector` is not a `uint64`, or `remotePoolAddress` is not hex bytes + */ +export function validateRemotePoolParams(operation: string, params: RemotePoolParams): string { + validateNonZeroAddress(operation, 'poolAddress', params.poolAddress) + validateUint64(operation, 'remoteChainSelector', params.remoteChainSelector) + return parseRemotePoolAddress(operation, params.remotePoolAddress) +} + +/** + * The three ops' {@link Operation.parse}: validates every field before any RPC and returns the + * params with `remotePoolAddress` already normalised, so `buildUnsigned` encodes it without + * re-parsing. Spreads the result of {@link validateRemotePoolParams} back over the params. + * @throws {@link CCTParamsInvalidError} if any field is invalid (see {@link validateRemotePoolParams}) + */ +export function parseRemotePoolParams( + operation: string, + params: RemotePoolParams, +): ParsedRemotePoolParams { + return { ...params, remotePoolAddress: validateRemotePoolParams(operation, params) } +} + +/** + * Reads the remote pool addresses currently registered on one lane, as the pool reports them. + * + * @remarks Scoped to the single `remoteChainSelector` rather than scanning every supported + * chain — one `getRemotePools` call instead of one per lane. + * + * A lane with no configuration at all surfaces from + * {@link EVMChain.getTokenPoolRemotes} as {@link CCIPTokenPoolChainConfigNotFoundError} (it + * requires a non-zero remote token), not as an empty result. That is treated here as "no remote + * pools registered", which is what it means: an unconfigured lane cannot have any. + */ +export async function readRegisteredRemotePools( + chain: EVMChain, + { poolAddress, remoteChainSelector }: RemotePoolParams, +): Promise { + let remotes + try { + remotes = await chain.getTokenPoolRemotes(poolAddress, remoteChainSelector) + } catch (err) { + if (err instanceof CCIPTokenPoolChainConfigNotFoundError) return [] + throw err + } + // one selector in, at most one lane out — keyed by the remote network's name + return Object.values(remotes).flatMap(({ remotePools }) => remotePools) +} + +/** + * Whether `remotePoolAddress` (hex bytes) is among the lane's `registered` pools. + * + * @remarks The two sides arrive in different spellings: `registered` comes back from + * {@link EVMChain.getTokenPoolRemotes} already decoded into the *remote* family's address format + * (checksummed hex for EVM, base58 for Solana, …), while the caller passes raw `bytes`. So the + * caller's value is decoded through the same codec, with the remote chain's family taken from + * its selector, and only then compared. + * + * Comparison is exact, or case-insensitive when both sides are hex — which covers EVM checksum + * spellings without risking a false match between two base58 addresses that differ only in case. + * If the family is unknown or the bytes are not decodable as one of its addresses (e.g. an + * oddly sized value), the undecoded hex is compared instead, so an unrecognised lane degrades + * to a plain byte comparison rather than throwing. + */ +export function isRegisteredRemotePool( + registered: readonly string[], + remotePoolAddress: string, + remoteChainSelector: bigint, +): boolean { + let expected + try { + expected = decodeAddress(remotePoolAddress, networkInfo(remoteChainSelector).family) + } catch { + expected = remotePoolAddress + } + return registered.some( + (pool) => + pool === expected || + (isHexString(pool) && isHexString(expected) && pool.toLowerCase() === expected.toLowerCase()), + ) +} diff --git a/ccip-sdk/src/cct/evm/token/contracts.ts b/ccip-sdk/src/cct/evm/token/contracts.ts new file mode 100644 index 000000000..3f0b10124 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/contracts.ts @@ -0,0 +1,234 @@ +/** + * EVM token contract layer for CCT: cached {@link Interface}s per {@link TokenVersion} + * ({@link getTokenInterface}) for read/write (e.g. ownership) ops, the deployable + * `CrossChainToken` (v2.0.0) artifact ({@link getTokenArtifact}), the token's role reads — the + * narrow predicate a role-gated write pre-flights ({@link readTokenRole}) and the informational + * role-set enumerations ({@link readTokenRoleHolders}) — and the owner read + * ({@link readTokenOwner}) plus the owner-only guard over it ({@link assertTokenOwner}). `2.0.0` + * is `CrossChainToken`; `1.5.1` / `1.6.2` are `FactoryBurnMintERC20`. Mirrors + * `token-pool/contracts.ts`. + * + * @packageDocumentation + */ + +import { Interface, getAddress, isError } from 'ethers' +import type { TypedContract } from 'ethers-abitype' + +import type { EVMChain } from '../../../evm/index.ts' +import { resultToObject } from '../../../evm/types.ts' +import { + CCTContractTypeInvalidError, + CCTContractVersionUnsupportedError, + CCTParamsInvalidError, +} from '../../errors.ts' +import FACTORY_BURN_MINT_ERC20_V1_5_1_ABI from '../artifacts/abi/V1_5_1/factory-burn-mint-erc20.ts' +import FACTORY_BURN_MINT_ERC20_V1_6_2_ABI from '../artifacts/abi/V1_6_2/factory-burn-mint-erc20.ts' +import CROSS_CHAIN_TOKEN_V2_0_0_ABI from '../artifacts/abi/V2_0_0/cross-chain-token.ts' +import CROSS_CHAIN_TOKEN_V2_0_0_BYTECODE from '../artifacts/bytecode/V2_0_0/cross-chain-token.ts' +import type { DeployArtifact } from '../operation.ts' +import { getTypedContract } from '../query.ts' + +/** + * Known token versions, low to high. `2.0.0` is `CrossChainToken`; `1.5.1` / `1.6.2` + * are `FactoryBurnMintERC20`. + */ +export const TokenVersion = { + V1_5_1: '1.5.1', + V1_6_2: '1.6.2', + V2_0_0: '2.0.0', +} as const + +/** A known token version. */ +export type TokenVersion = (typeof TokenVersion)[keyof typeof TokenVersion] + +/** + * Cached token {@link Interface}s per {@link TokenVersion}, built once from the vendored ABIs + * (no per-call `new Interface`) — for read/write (e.g. ownership) ops. Mirrors + * `TOKEN_POOL_INTERFACES` in `token-pool/contracts.ts`. + */ +export const TOKEN_INTERFACES: Record = { + [TokenVersion.V1_5_1]: new Interface(FACTORY_BURN_MINT_ERC20_V1_5_1_ABI), + [TokenVersion.V1_6_2]: new Interface(FACTORY_BURN_MINT_ERC20_V1_6_2_ABI), + [TokenVersion.V2_0_0]: new Interface(CROSS_CHAIN_TOKEN_V2_0_0_ABI), +} + +/** Returns the cached token {@link Interface} for `version`. */ +export function getTokenInterface(version: TokenVersion): Interface { + return TOKEN_INTERFACES[version] +} + +/** + * The interface every BurnMintERC677 role/mint write encodes through. + * + * Pinned to v1.5.1: the role functions, `mint`, and the role reads are identical at v1.6.2 and + * on `HyperLiquidCompatibleERC20 1.6.2`, so there is nothing to dispatch on. v2.0.0's + * `CrossChainToken` is a different contract, ruled out by {@link readTokenRole}. + */ +export function getErc20Token(): Interface { + return TOKEN_INTERFACES[TokenVersion.V1_5_1] +} + +/** + * Deploy artifacts ({@link DeployArtifact}: contract name + ctor {@link Interface} + creation + * bytecode) keyed by {@link TokenVersion}, built once; read via {@link getTokenArtifact}. Only + * `2.0.0` (`CrossChainToken`) is deployable. + */ +export const TOKEN_ARTIFACTS: Partial> = { + [TokenVersion.V2_0_0]: { + contract: 'CrossChainToken', + iface: TOKEN_INTERFACES[TokenVersion.V2_0_0], + bytecode: CROSS_CHAIN_TOKEN_V2_0_0_BYTECODE, + }, +} + +/** + * Returns the cached deploy artifact for `version`. + * @throws {@link CCTContractVersionUnsupportedError} if `version` has no vendored deploy bytecode + */ +export function getTokenArtifact(version: TokenVersion): DeployArtifact { + const artifact = TOKEN_ARTIFACTS[version] + if (!artifact) throw new CCTContractVersionUnsupportedError('token', version) + return artifact +} + +/** + * True for the two failure shapes a call to a function a contract does not declare produces: + * `CALL_EXCEPTION` (revert) and `BAD_DATA` (node answers `0x`). Deliberately narrow — a transport + * error or rate limit must not be read as "this contract lacks the function". + */ +function isMissingFunction(err: unknown): boolean { + return isError(err, 'CALL_EXCEPTION') || isError(err, 'BAD_DATA') +} + +/** The two role predicates, declared identically by every BurnMintERC677 token. */ +type TokenRoleReader = Pick< + TypedContract, + 'isMinter' | 'isBurner' +> + +/** + * Reads whether `account` holds one of a BurnMintERC677 token's roles, in a single `eth_call`. + * + * Doubles as the family check every role/mint write needs: only the BurnMintERC677 family + * declares these predicates, so a v2.0.0 `CrossChainToken`, a token pool, or an EOA fails here + * before an op can hand back calldata aimed at code that cannot run it. No `version` parameter — + * both predicates are identical at v1.5.1 and v1.6.2 (see {@link getErc20Token}). + * @param chain - Chain to read from. + * @param tokenAddress - Token contract to read from. + * @param read - Which role predicate to call. + * @param account - Address to test. + * @returns Whether `account` currently holds that role. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` does not declare `read` — it is + * not a BurnMintERC677 token + */ +export async function readTokenRole( + chain: EVMChain, + tokenAddress: string, + read: 'isMinter' | 'isBurner', + account: string, +): Promise { + const token: TokenRoleReader = getTypedContract( + chain, + tokenAddress, + FACTORY_BURN_MINT_ERC20_V1_5_1_ABI, + ) + try { + return await token[read](account) + } catch (err) { + if (!isMissingFunction(err)) throw err + throw new CCTContractTypeInvalidError( + tokenAddress, + 'BurnMintERC677 token (FactoryBurnMintERC20 v1.5.1 / v1.6.2)', + // the type is genuinely unknown: the contract answered nothing + 'unknown', + `it does not declare ${read}(address) — a v2.0.0 CrossChainToken gates mint/burn through AccessControl instead, and support for it ships separately`, + { cause: err instanceof Error ? err : undefined }, + ) + } +} + +/** The two role-set getters, declared identically by every BurnMintERC677 token. */ +type TokenRoleHolderReader = Pick< + TypedContract, + 'getMinters' | 'getBurners' +> + +/** + * Reads the full set of accounts holding one of a BurnMintERC677 token's roles, in a single + * `eth_call`. + * + * Informational, for audit and UX; checking one address is {@link readTokenRole}, not this set + * plus a client-side scan. Same family check and version reasoning as that read: only this family + * enumerates its role members, and both getters are identical at v1.5.1 and v1.6.2. + * @param chain - Chain to read from. + * @param tokenAddress - Token contract to read from. + * @param read - Which role set to enumerate. + * @returns The current holders, checksummed, in the order the token returns them. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` does not declare `read` — it is + * not a BurnMintERC677 token + */ +export async function readTokenRoleHolders( + chain: EVMChain, + tokenAddress: string, + read: 'getMinters' | 'getBurners', +): Promise { + const token: TokenRoleHolderReader = getTypedContract( + chain, + tokenAddress, + FACTORY_BURN_MINT_ERC20_V1_5_1_ABI, + ) + try { + // the abitype handle types an `address[]` return as `(string | Addressable)[]` + return (await token[read]()).map((holder) => getAddress(holder as string)) + } catch (err) { + if (!isMissingFunction(err)) throw err + throw new CCTContractTypeInvalidError( + tokenAddress, + 'BurnMintERC677 token (FactoryBurnMintERC20 v1.5.1 / v1.6.2)', + // the type is genuinely unknown: the contract answered nothing + 'unknown', + `it does not declare ${read}() — a v2.0.0 CrossChainToken gates mint/burn through AccessControl, which does not enumerate role members`, + { cause: err instanceof Error ? err : undefined }, + ) + } +} + +/** `Ownable2Step.owner()`, declared identically by every supported token. */ +type TokenOwnerGetter = Pick, 'owner'> + +/** + * Reads a token's Ownable2Step `owner()` in a single `eth_call`. On the BurnMintERC677 family the + * owner *is* the mint/burn role admin — `grantMintRole` and its siblings are `onlyOwner`. + * @param chain - Chain to read from. + * @param tokenAddress - Token contract to read `owner()` from. + * @returns The current owner, checksummed. + */ +export async function readTokenOwner(chain: EVMChain, tokenAddress: string): Promise { + const token: TokenOwnerGetter = getTypedContract( + chain, + tokenAddress, + FACTORY_BURN_MINT_ERC20_V1_5_1_ABI, + ) + return getAddress(resultToObject(await token.owner())) +} + +/** + * Pre-flights `sender` against the token's on-chain `owner()` for an owner-gated write, so an + * unauthorized caller fails as a {@link CCTParamsInvalidError} here instead of as an opaque + * `OnlyOwner` revert after a multisig has already reviewed and signed. + * @param operation - Operation name, for the error's `operation` field. + * @param chain - Chain to read the owner from. + * @param tokenAddress - Token being written to. + * @param sender - The address the tx will be sent from; compared checksummed. + * @throws {@link CCTParamsInvalidError} if `sender` is not the token owner + */ +export async function assertTokenOwner( + operation: string, + chain: EVMChain, + tokenAddress: string, + sender: string, +): Promise { + const owner = await readTokenOwner(chain, tokenAddress) + if (getAddress(sender) === owner) return + throw new CCTParamsInvalidError(operation, 'sender', `must be the current token owner (${owner})`) +} diff --git a/ccip-sdk/src/cct/evm/token/operations/deploy-token.test.ts b/ccip-sdk/src/cct/evm/token/operations/deploy-token.test.ts new file mode 100644 index 000000000..07276b2ff --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/deploy-token.test.ts @@ -0,0 +1,271 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { ZeroAddress, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { CCTParamsInvalidError, CCTTxFailedError } from '../../../errors.ts' +import crossChainBytecode from '../../artifacts/bytecode/V2_0_0/cross-chain-token.ts' +import { DeployToken } from './deploy-token.ts' + +const SENDER = '0x' + '11'.repeat(20) +const OWNER = '0x' + '11'.repeat(20) +const CCIP_ADMIN = '0x' + '22'.repeat(20) +const ROLE_ADMIN = '0x' + '33'.repeat(20) +const PREMINT_RECIPIENT = '0x' + '44'.repeat(20) +const DEPLOYED = '0x' + '77'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +// Golden vector: a pinned constructor-arg encoding for the fixed inputs below. Independent of +// the SDK encoder — it guards CrossChainToken's init-code (bytecode + constructor) against drift. + +// CrossChainToken ctor: ((name, symbol, maxSupply, preMint, preMintRecipient, decimals, +// ccipAdmin), burnMintRoleAdmin, owner). +const INPUTS = { + name: 'CCIP Test Token', + symbol: 'CCIPT', + decimals: 18, + maxSupply: 0n, + preMint: 1000n, + preMintRecipient: PREMINT_RECIPIENT, + ccipAdmin: CCIP_ADMIN, + burnMintRoleAdmin: ROLE_ADMIN, + owner: OWNER, +} +const CTOR_ARGS = + '0000000000000000000000000000000000000000000000000000000000000060' + + '0000000000000000000000003333333333333333333333333333333333333333' + + '0000000000000000000000001111111111111111111111111111111111111111' + + '00000000000000000000000000000000000000000000000000000000000000e0' + + '0000000000000000000000000000000000000000000000000000000000000120' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '00000000000000000000000000000000000000000000000000000000000003e8' + + '0000000000000000000000004444444444444444444444444444444444444444' + + '0000000000000000000000000000000000000000000000000000000000000012' + + '0000000000000000000000002222222222222222222222222222222222222222' + + '000000000000000000000000000000000000000000000000000000000000000f' + + '43434950205465737420546f6b656e0000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000005' + + '4343495054000000000000000000000000000000000000000000000000000000' +const DEPLOY_DATA = crossChainBytecode + CTOR_ARGS + +/** Minimal EVMChain stub — deployToken's build path ignores it; execute uses only these. */ +function stubChain(): EVMChain { + return { + provider: {} as never, + logger: { debug() {}, info() {}, warn() {}, error() {} }, + nextNonce: async () => 0, + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +/** Fake ethers Signer whose deployment receipt carries `contractAddress`. */ +function fakeSigner(opts: { contractAddress?: string | null; waitError?: Error }) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(SENDER), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => + opts.waitError + ? Promise.reject(opts.waitError) + : Promise.resolve({ + status: 1, + contractAddress: + opts.contractAddress === undefined ? DEPLOYED : opts.contractAddress, + }), + }), + } +} + +describe('DeployToken (cct/evm)', () => { + it('builds a deployment as init-code with no `to` (golden vector)', async () => { + const unsigned = await new DeployToken().generate(stubChain(), { ...INPUTS, sender: SENDER }) + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + const tx = unsigned.transactions[0]! + assert.equal(tx.to, undefined, 'deployment tx has no `to`') + assert.equal(tx.from, SENDER) + assert.ok(tx.data!.startsWith(crossChainBytecode), 'data starts with creation bytecode') + assert.equal(tx.data, DEPLOY_DATA) + }) + + it('omits `from` when no sender is given', async () => { + const unsigned = await new DeployToken().generate(stubChain(), INPUTS) + assert.equal(unsigned.transactions[0]!.from, undefined) + }) + + it('defaults preMint to 0 and a zero preMintRecipient when both omitted', async () => { + const { preMint: _preMint, preMintRecipient: _recipient, ...zeroPreMint } = INPUTS + const unsigned = await new DeployToken().generate(stubChain(), zeroPreMint) + // preMint 0 must pair with the zero address, else CrossChainToken's ctor reverts. + const expected = DEPLOY_DATA.replace( + '00000000000000000000000000000000000000000000000000000000000003e8', + '0'.repeat(64), + ).replace('0000000000000000000000004444444444444444444444444444444444444444', '0'.repeat(64)) + assert.equal(unsigned.transactions[0]!.data, expected) + }) + + it('defaults ccipAdmin/burnMintRoleAdmin to owner when omitted', async () => { + const omitted = await new DeployToken().generate(stubChain(), { + name: 'CCIP Test Token', + symbol: 'CCIPT', + decimals: 18, + maxSupply: 0n, + owner: OWNER, + }) + const explicit = await new DeployToken().generate(stubChain(), { + name: 'CCIP Test Token', + symbol: 'CCIPT', + decimals: 18, + maxSupply: 0n, + ccipAdmin: OWNER, + burnMintRoleAdmin: OWNER, + owner: OWNER, + }) + assert.equal(omitted.transactions[0]!.data, explicit.transactions[0]!.data) + }) + + it('rejects a missing preMintRecipient when preMint > 0', async () => { + const { preMintRecipient: _recipient, ...withoutRecipient } = INPUTS + await assert.rejects( + () => new DeployToken().generate(stubChain(), withoutRecipient), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'preMintRecipient', + ) + }) + + it('rejects a preMintRecipient when preMint is 0', async () => { + await assert.rejects( + () => + new DeployToken().generate(stubChain(), { + ...INPUTS, + preMint: 0n, + preMintRecipient: PREMINT_RECIPIENT, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'preMintRecipient', + ) + }) + + it('rejects a zero-address preMintRecipient when preMint > 0', async () => { + await assert.rejects( + () => new DeployToken().generate(stubChain(), { ...INPUTS, preMintRecipient: ZeroAddress }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'preMintRecipient', + ) + }) + + it('rejects an empty name, tagged with the operation and param', async () => { + await assert.rejects( + () => new DeployToken().generate(stubChain(), { ...INPUTS, name: '' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'deployToken' && + err.context.param === 'name', + ) + }) + + it('rejects decimals outside 0–255', async () => { + await assert.rejects( + () => new DeployToken().generate(stubChain(), { ...INPUTS, decimals: 256 }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'decimals', + ) + }) + + it('rejects a maxSupply above uint256 max', async () => { + await assert.rejects( + () => new DeployToken().generate(stubChain(), { ...INPUTS, maxSupply: 2n ** 256n }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'maxSupply', + ) + }) + + it('rejects an invalid owner', async () => { + await assert.rejects( + () => new DeployToken().generate(stubChain(), { ...INPUTS, owner: 'nope' }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'owner', + ) + }) + + it('rejects an invalid ccipAdmin', async () => { + await assert.rejects( + () => new DeployToken().generate(stubChain(), { ...INPUTS, ccipAdmin: 'nope' }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'ccipAdmin', + ) + }) + + it('rejects preMint greater than a capped maxSupply', async () => { + await assert.rejects( + () => new DeployToken().generate(stubChain(), { ...INPUTS, maxSupply: 10n, preMint: 11n }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'preMint', + ) + }) + + it('rejects an invalid sender', async () => { + await assert.rejects( + () => new DeployToken().generate(stubChain(), { ...INPUTS, sender: 'nope' }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('deploys and returns the tx hash and deployed address', async () => { + const result = await new DeployToken().execute(stubChain(), { + ...INPUTS, + wallet: fakeSigner({ contractAddress: DEPLOYED }), + }) + assert.deepEqual(result, { + hash: HASH, + contractAddress: DEPLOYED, + verification: { contract: 'CrossChainToken', encodedConstructorArgs: '0x' + CTOR_ARGS }, + }) + }) + + it('carries the verification handle recovered from the init-code', async () => { + const result = await new DeployToken().execute(stubChain(), { + ...INPUTS, + wallet: fakeSigner({ contractAddress: DEPLOYED }), + }) + assert.equal(result.verification.contract, 'CrossChainToken') + assert.equal(result.verification.encodedConstructorArgs, '0x' + CTOR_ARGS) + }) + + it('throws CCTTxFailedError when the receipt carries no contract address', async () => { + await assert.rejects( + () => + new DeployToken().execute(stubChain(), { + ...INPUTS, + wallet: fakeSigner({ contractAddress: null }), + }), + (err: unknown) => + err instanceof CCTTxFailedError && + err.context.operation === 'deployToken' && + !err.isTransient, + ) + }) + + it('throws CCIPExecTxRevertedError when the deployment reverts on-chain', async () => { + await assert.rejects( + () => + new DeployToken().execute(stubChain(), { + ...INPUTS, + wallet: fakeSigner({ waitError: makeError('execution reverted', 'CALL_EXCEPTION') }), + }), + (err: unknown) => + err instanceof CCIPExecTxRevertedError && + err.context.operation === 'deployToken' && + err.context.txHash === HASH, + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => new DeployToken().execute(stubChain(), { ...INPUTS, wallet: {} }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token/operations/deploy-token.ts b/ccip-sdk/src/cct/evm/token/operations/deploy-token.ts new file mode 100644 index 000000000..acfa3c20f --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/deploy-token.ts @@ -0,0 +1,113 @@ +/** + * deployToken — deploys a `CrossChainToken` (v2.0.0) via raw init-code. The tx has no + * `to`; `execute` returns the deployed contract address. + * + * @packageDocumentation + */ + +import { type Interface, ZeroAddress } from 'ethers' + +import { CCTParamsInvalidError } from '../../../errors.ts' +import { type DeployArtifact, EVMDeployOperation } from '../../operation.ts' +import { + validateAddress, + validateNonEmptyString, + validateUint256, + validateUint8, +} from '../../validate.ts' +import { TokenVersion, getTokenArtifact } from '../contracts.ts' + +/** Parameters for {@link DeployToken} — deploys `CrossChainToken` (v2.0.0). */ +export interface DeployTokenParams { + name: string + symbol: string + decimals: number + /** Max supply cap; `0n` means unlimited. */ + maxSupply: bigint + /** Amount minted at deploy; defaults to `0n`. Must be `<= maxSupply` when capped. */ + preMint?: bigint + /** Receives ownership; a valid address. */ + owner: string + /** Recipient of `preMint`; required when `preMint > 0`, must be unset otherwise. */ + preMintRecipient?: string + /** CCIP admin (`getCCIPAdmin`); defaults to `owner`. */ + ccipAdmin?: string + /** Admin of the burn/mint roles; defaults to `owner`. */ + burnMintRoleAdmin?: string + sender?: string +} + +/** Encodes the `CrossChainToken` (v2.0.0) constructor args; admins default to `owner`. */ +function encodeCrossChainToken(iface: Interface, p: DeployTokenParams): string { + return iface.encodeDeploy([ + [ + p.name, + p.symbol, + p.maxSupply, + p.preMint ?? 0n, + // preMintRecipient is set iff preMint > 0 (enforced in validate); zero address otherwise. + p.preMintRecipient ?? ZeroAddress, + p.decimals, + p.ccipAdmin ?? p.owner, + ], + p.burnMintRoleAdmin ?? p.owner, + p.owner, + ]) +} + +/** Deploys a `CrossChainToken`; `execute` resolves to `{ hash, contractAddress, verification }`. */ +export class DeployToken extends EVMDeployOperation { + readonly name = 'deployToken' + + /** Validates the constructor params before building init-code. */ + protected override validate(params: DeployTokenParams): void { + validateNonEmptyString(this.name, 'name', params.name) + validateNonEmptyString(this.name, 'symbol', params.symbol) + validateUint8(this.name, 'decimals', params.decimals) + validateUint256(this.name, 'maxSupply', params.maxSupply) + const preMint = params.preMint ?? 0n + validateUint256(this.name, 'preMint', preMint) + validateAddress(this.name, 'owner', params.owner) + if (params.maxSupply !== 0n && preMint > params.maxSupply) + throw new CCTParamsInvalidError( + this.name, + 'preMint', + `must be <= maxSupply (${params.maxSupply}), got ${preMint}`, + ) + // Mirror CrossChainToken's ctor: preMintRecipient is set (and non-zero) iff preMint > 0. + if (preMint > 0n) { + if (params.preMintRecipient === undefined) + throw new CCTParamsInvalidError( + this.name, + 'preMintRecipient', + 'must be set when preMint > 0', + ) + validateAddress(this.name, 'preMintRecipient', params.preMintRecipient) + if (params.preMintRecipient === ZeroAddress) + throw new CCTParamsInvalidError( + this.name, + 'preMintRecipient', + 'must be non-zero when preMint > 0', + ) + } else if (params.preMintRecipient !== undefined) { + throw new CCTParamsInvalidError( + this.name, + 'preMintRecipient', + 'must be unset when preMint is 0', + ) + } + if (params.ccipAdmin !== undefined) validateAddress(this.name, 'ccipAdmin', params.ccipAdmin) + if (params.burnMintRoleAdmin !== undefined) + validateAddress(this.name, 'burnMintRoleAdmin', params.burnMintRoleAdmin) + } + + /** Deploy artifact for `CrossChainToken` (v2.0.0). */ + protected artifact(): DeployArtifact { + return getTokenArtifact(TokenVersion.V2_0_0) + } + + /** ABI-encodes the `CrossChainToken` (v2.0.0) constructor args. */ + protected encode(iface: Interface, params: DeployTokenParams): string { + return encodeCrossChainToken(iface, params) + } +} diff --git a/ccip-sdk/src/cct/evm/token/operations/get-burners.test.ts b/ccip-sdk/src/cct/evm/token/operations/get-burners.test.ts new file mode 100644 index 000000000..26e0d9d05 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/get-burners.test.ts @@ -0,0 +1,94 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import { CCTContractTypeInvalidError, CCTParamsInvalidError } from '../../../errors.ts' +import { GetBurners } from './get-burners.ts' + +const TOKEN = '0x' + '11'.repeat(20) +const HOLDER_A = '0x' + '22'.repeat(20) +const HOLDER_B = '0x' + '33'.repeat(20) + +/** Read results from a fresh Interface, never the SDK's cached one. */ +const FRESH = new Interface(['function getBurners() view returns (address[])']) + +/** The `eth_call`s the op makes, in order, as decoded function names. */ +type Seen = { calls: string[] } +const newSeen = (): Seen => ({ calls: [] }) + +/** EVMChain stub answering `getBurners()` with `holders`. */ +function stubChain({ + holders = [HOLDER_A, HOLDER_B] as string[], + callError, + seen = newSeen(), +}: { + holders?: string[] + /** Fails every `eth_call`, standing in for a contract that is not a BurnMintERC677 token. */ + callError?: Error + seen?: Seen +} = {}): EVMChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + provider: { + call: ({ data }: { data: string }) => { + if (callError) return Promise.reject(callError) + const fn = FRESH.getFunction(data.slice(0, 10))!.name + seen.calls.push(fn) + return Promise.resolve(FRESH.encodeFunctionResult(fn, [holders])) + }, + }, + } as unknown as EVMChain +} + +const op = new GetBurners() + +describe('GetBurners (cct/evm)', () => { + it('lists the holders in one call', async () => { + const seen = newSeen() + const holders = await op.query(stubChain({ seen }), { tokenAddress: TOKEN }) + + assert.deepEqual(holders, [HOLDER_A, HOLDER_B]) + assert.deepEqual(seen.calls, ['getBurners']) + }) + + it('returns an empty list when no account holds the role', async () => { + assert.deepEqual(await op.query(stubChain({ holders: [] }), { tokenAddress: TOKEN }), []) + }) + + it('checksums the addresses the token returns', async () => { + const chain = stubChain({ holders: [HOLDER_A.toLowerCase()] }) + assert.deepEqual(await op.query(chain, { tokenAddress: TOKEN }), [HOLDER_A]) + }) + + for (const value of ['not-an-address', ZeroAddress]) { + it(`rejects tokenAddress = ${value} before any RPC`, async () => { + const seen = newSeen() + await assert.rejects( + () => op.query(stubChain({ seen }), { tokenAddress: value }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'getBurners' && + err.context.param === 'tokenAddress', + ) + assert.deepEqual(seen.calls, []) + }) + } + + it('rejects a contract that does not declare getBurners()', async () => { + // a v2.0.0 CrossChainToken does not enumerate its AccessControl role members + const revert = makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + await assert.rejects( + () => op.query(stubChain({ callError: revert }), { tokenAddress: TOKEN }), + (err: unknown) => err instanceof CCTContractTypeInvalidError && err.context.address === TOKEN, + ) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token/operations/get-burners.ts b/ccip-sdk/src/cct/evm/token/operations/get-burners.ts new file mode 100644 index 000000000..9fc624deb --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/get-burners.ts @@ -0,0 +1,44 @@ +/** + * getBurners — lists every account holding a BurnMintERC677 token's burn role. Informational + * (audit / UX): a *check* of one address is `isBurner`, not a scan of this set. + * + * @packageDocumentation + */ + +import type { EVMChain } from '../../../../evm/index.ts' +import { EVMQuery } from '../../query.ts' +import { validateNonZeroAddress } from '../../validate.ts' +import { readTokenRoleHolders } from '../contracts.ts' + +/** Parameters for {@link GetBurners}. */ +export type GetBurnersParams = { + /** BurnMintERC677 token (v1.5.1 / v1.6.2) to read. */ + tokenAddress: string +} + +/** Result of {@link GetBurners}: the burn-role holders, checksummed, in the token's own order. */ +export type GetBurnersResult = string[] + +/** Lists the accounts holding a BurnMintERC677 token's burn role, via `getBurners()`. */ +export class GetBurners extends EVMQuery { + readonly name = 'getBurners' + + /** + * Validates the token address; nothing to convert for {@link read}. + * @throws {@link CCTParamsInvalidError} if `tokenAddress` is not a valid, non-zero address + */ + protected prepare(params: GetBurnersParams): GetBurnersParams { + validateNonZeroAddress(this.name, 'tokenAddress', params.tokenAddress) + return params + } + + /** + * Enumerates the role set in a single `eth_call`. + * @remarks No version resolution: `getBurners()` is identical at v1.5.1 and v1.6.2, and a + * contract that does not declare it is reported by {@link readTokenRoleHolders}. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + */ + protected read(chain: EVMChain, { tokenAddress }: GetBurnersParams): Promise { + return readTokenRoleHolders(chain, tokenAddress, 'getBurners') + } +} diff --git a/ccip-sdk/src/cct/evm/token/operations/get-minters.test.ts b/ccip-sdk/src/cct/evm/token/operations/get-minters.test.ts new file mode 100644 index 000000000..eb8710d2f --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/get-minters.test.ts @@ -0,0 +1,94 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import { CCTContractTypeInvalidError, CCTParamsInvalidError } from '../../../errors.ts' +import { GetMinters } from './get-minters.ts' + +const TOKEN = '0x' + '11'.repeat(20) +const HOLDER_A = '0x' + '22'.repeat(20) +const HOLDER_B = '0x' + '33'.repeat(20) + +/** Read results from a fresh Interface, never the SDK's cached one. */ +const FRESH = new Interface(['function getMinters() view returns (address[])']) + +/** The `eth_call`s the op makes, in order, as decoded function names. */ +type Seen = { calls: string[] } +const newSeen = (): Seen => ({ calls: [] }) + +/** EVMChain stub answering `getMinters()` with `holders`. */ +function stubChain({ + holders = [HOLDER_A, HOLDER_B] as string[], + callError, + seen = newSeen(), +}: { + holders?: string[] + /** Fails every `eth_call`, standing in for a contract that is not a BurnMintERC677 token. */ + callError?: Error + seen?: Seen +} = {}): EVMChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + provider: { + call: ({ data }: { data: string }) => { + if (callError) return Promise.reject(callError) + const fn = FRESH.getFunction(data.slice(0, 10))!.name + seen.calls.push(fn) + return Promise.resolve(FRESH.encodeFunctionResult(fn, [holders])) + }, + }, + } as unknown as EVMChain +} + +const op = new GetMinters() + +describe('GetMinters (cct/evm)', () => { + it('lists the holders in one call', async () => { + const seen = newSeen() + const holders = await op.query(stubChain({ seen }), { tokenAddress: TOKEN }) + + assert.deepEqual(holders, [HOLDER_A, HOLDER_B]) + assert.deepEqual(seen.calls, ['getMinters']) + }) + + it('returns an empty list when no account holds the role', async () => { + assert.deepEqual(await op.query(stubChain({ holders: [] }), { tokenAddress: TOKEN }), []) + }) + + it('checksums the addresses the token returns', async () => { + const chain = stubChain({ holders: [HOLDER_A.toLowerCase()] }) + assert.deepEqual(await op.query(chain, { tokenAddress: TOKEN }), [HOLDER_A]) + }) + + for (const value of ['not-an-address', ZeroAddress]) { + it(`rejects tokenAddress = ${value} before any RPC`, async () => { + const seen = newSeen() + await assert.rejects( + () => op.query(stubChain({ seen }), { tokenAddress: value }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'getMinters' && + err.context.param === 'tokenAddress', + ) + assert.deepEqual(seen.calls, []) + }) + } + + it('rejects a contract that does not declare getMinters()', async () => { + // a v2.0.0 CrossChainToken does not enumerate its AccessControl role members + const revert = makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + await assert.rejects( + () => op.query(stubChain({ callError: revert }), { tokenAddress: TOKEN }), + (err: unknown) => err instanceof CCTContractTypeInvalidError && err.context.address === TOKEN, + ) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token/operations/get-minters.ts b/ccip-sdk/src/cct/evm/token/operations/get-minters.ts new file mode 100644 index 000000000..f25f3ff2a --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/get-minters.ts @@ -0,0 +1,44 @@ +/** + * getMinters — lists every account holding a BurnMintERC677 token's mint role. Informational + * (audit / UX): a *check* of one address is `isMinter`, not a scan of this set. + * + * @packageDocumentation + */ + +import type { EVMChain } from '../../../../evm/index.ts' +import { EVMQuery } from '../../query.ts' +import { validateNonZeroAddress } from '../../validate.ts' +import { readTokenRoleHolders } from '../contracts.ts' + +/** Parameters for {@link GetMinters}. */ +export type GetMintersParams = { + /** BurnMintERC677 token (v1.5.1 / v1.6.2) to read. */ + tokenAddress: string +} + +/** Result of {@link GetMinters}: the mint-role holders, checksummed, in the token's own order. */ +export type GetMintersResult = string[] + +/** Lists the accounts holding a BurnMintERC677 token's mint role, via `getMinters()`. */ +export class GetMinters extends EVMQuery { + readonly name = 'getMinters' + + /** + * Validates the token address; nothing to convert for {@link read}. + * @throws {@link CCTParamsInvalidError} if `tokenAddress` is not a valid, non-zero address + */ + protected prepare(params: GetMintersParams): GetMintersParams { + validateNonZeroAddress(this.name, 'tokenAddress', params.tokenAddress) + return params + } + + /** + * Enumerates the role set in a single `eth_call`. + * @remarks No version resolution: `getMinters()` is identical at v1.5.1 and v1.6.2, and a + * contract that does not declare it is reported by {@link readTokenRoleHolders}. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + */ + protected read(chain: EVMChain, { tokenAddress }: GetMintersParams): Promise { + return readTokenRoleHolders(chain, tokenAddress, 'getMinters') + } +} diff --git a/ccip-sdk/src/cct/evm/token/operations/grant-burn-role.test.ts b/ccip-sdk/src/cct/evm/token/operations/grant-burn-role.test.ts new file mode 100644 index 000000000..0f6169a57 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/grant-burn-role.test.ts @@ -0,0 +1,260 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { CCTContractTypeInvalidError, CCTParamsInvalidError } from '../../../errors.ts' +import { type GrantBurnRoleParams, GrantBurnRole } from './grant-burn-role.ts' + +const TOKEN = '0x' + '11'.repeat(20) +const OWNER = '0x' + '22'.repeat(20) +const ACCOUNT = '0x' + '33'.repeat(20) +const NOT_THE_OWNER = '0x' + '44'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +/** Calldata built from a fresh Interface — never the SDK's cached one, or this proves nothing. */ +const FRESH = new Interface([ + 'function grantBurnRole(address burner)', + 'function isBurner(address burner) view returns (bool)', + 'function owner() view returns (address)', +]) +const expectedData = (burner = ACCOUNT) => FRESH.encodeFunctionData('grantBurnRole', [burner]) + +/** The `eth_call`s the op makes, in order, as decoded function names. */ +type Seen = { calls: string[] } +const newSeen = (): Seen => ({ calls: [] }) + +/** + * EVMChain stub for a BurnMintERC677 token owned by `OWNER`. `holdsRole` is the pre-existing + * role state of the account being granted — false is the state that makes this call a real change. + */ +function stubChain({ + holdsRole = false, + owner = OWNER, + callError, + seen = newSeen(), +}: { + holdsRole?: boolean + owner?: string + /** Fails every `eth_call`, standing in for a contract that is not a BurnMintERC677 token. */ + callError?: Error + seen?: Seen +} = {}): EVMChain { + const results: Record = { isBurner: [holdsRole], owner: [owner] } + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + provider: { + call: ({ data }: { data: string }) => { + if (callError) return Promise.reject(callError) + const fn = FRESH.getFunction(data.slice(0, 10))!.name + seen.calls.push(fn) + return Promise.resolve(FRESH.encodeFunctionResult(fn, results[fn])) + }, + }, + nextNonce: () => Promise.resolve(0), + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +function fakeSigner(waitError?: Error, address = OWNER) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => (waitError ? Promise.reject(waitError) : Promise.resolve({ status: 1 })), + }), + } +} + +/** A revert with no data, the shape a call to an undeclared function produces. */ +const missingFunction = () => + makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + +const op = new GrantBurnRole() + +function generate(chain: EVMChain, overrides: Partial = {}) { + return op.generate(chain, { tokenAddress: TOKEN, burner: ACCOUNT, sender: OWNER, ...overrides }) +} + +describe('GrantBurnRole (cct/evm)', () => { + describe('generate', () => { + it('encodes grantBurnRole(address) to the token', async () => { + const seen = newSeen() + const unsigned = await generate(stubChain({ seen })) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, TOKEN) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, expectedData()) + // the role read comes first: it is also the family check, so it gates the owner read + assert.deepEqual(seen.calls, ['isBurner', 'owner']) + }) + + it('omits from when sender is not supplied, but still probes the token', async () => { + const seen = newSeen() + const unsigned = await generate(stubChain({ seen }), { sender: undefined }) + + assert.equal(unsigned.transactions[0]!.from, undefined) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + // no sender to compare, so the owner read is skipped — the family check is not + assert.deepEqual(seen.calls, ['isBurner']) + }) + }) + + describe('validation', () => { + for (const [param, value] of [ + ['tokenAddress', 'not-an-address'], + // a tx to `0x0` hits no code, so it mines as a successful no-op instead of reverting + ['tokenAddress', ZeroAddress], + ['burner', 'not-an-address'], + ['burner', ZeroAddress], + ['sender', 'not-an-address'], + ] as const) { + it(`rejects ${param} = ${String(value)} before any RPC`, async () => { + const seen = newSeen() + await assert.rejects( + () => generate(stubChain({ seen }), { [param]: value }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'grantBurnRole' && + err.context.param === param, + ) + assert.deepEqual(seen.calls, []) + }) + } + }) + + describe('family check', () => { + it('rejects a contract that is not a BurnMintERC677 token', async () => { + // a v2.0.0 CrossChainToken, a token pool, and an EOA all fail the isBurner read + await assert.rejects( + () => generate(stubChain({ callError: missingFunction() })), + (err: unknown) => + err instanceof CCTContractTypeInvalidError && err.context.address === TOKEN, + ) + }) + + it('rejects an unrelated address even with no sender to check', async () => { + // the case an owner gate alone would miss: a token pool declares owner() too + await assert.rejects( + () => generate(stubChain({ callError: missingFunction() }), { sender: undefined }), + (err: unknown) => err instanceof CCTContractTypeInvalidError, + ) + }) + }) + + describe('no-op guard', () => { + it('rejects a grant when the account already holds the burn role', async () => { + // stricter than the chain: the role set is an EnumerableSet, so this would mine as a + // silent no-op rather than revert + const seen = newSeen() + await assert.rejects( + () => generate(stubChain({ holdsRole: true, seen })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'grantBurnRole' && + err.context.param === 'burner' && + /already holds the burn role/.test(String(err.context.reason)), + ) + // rejected on the role read alone, before the owner read + assert.deepEqual(seen.calls, ['isBurner']) + }) + + it('rejects the no-op with no sender supplied too', async () => { + await assert.rejects( + () => generate(stubChain({ holdsRole: true }), { sender: undefined }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'burner', + ) + }) + }) + + describe('owner gate', () => { + it('rejects a sender that does not own the token', async () => { + await assert.rejects( + () => generate(stubChain(), { sender: NOT_THE_OWNER }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'sender' && + /must be the current token owner/.test(String(err.context.reason)), + ) + }) + }) + + describe('execute', () => { + it('submits as the token owner and returns the tx hash', async () => { + const { hash } = await op.execute(stubChain(), { + tokenAddress: TOKEN, + burner: ACCOUNT, + wallet: fakeSigner(), + }) + assert.equal(hash, HASH) + }) + + it('rejects a sender that is not the signing wallet', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + burner: ACCOUNT, + sender: NOT_THE_OWNER, + wallet: fakeSigner(), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects a wallet that does not own the token', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + burner: ACCOUNT, + wallet: fakeSigner(undefined, NOT_THE_OWNER), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('surfaces an on-chain revert', async () => { + const revert = makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'sendTransaction', + data: '0x', + reason: 'OnlyOwner', + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + burner: ACCOUNT, + wallet: fakeSigner(revert), + }), + (err: unknown) => err instanceof CCIPExecTxRevertedError, + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { tokenAddress: TOKEN, burner: ACCOUNT, wallet: {} }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token/operations/grant-burn-role.ts b/ccip-sdk/src/cct/evm/token/operations/grant-burn-role.ts new file mode 100644 index 000000000..7517355e1 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/grant-burn-role.ts @@ -0,0 +1,80 @@ +/** + * grantBurnRole: grants a BurnMintERC677 token's burn role to one account. Owner-gated + * (`onlyOwner`); the owner is the token's mint/burn role admin. + * + * @packageDocumentation + */ + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { validateNonZeroAddress } from '../../validate.ts' +import { assertTokenOwner, getErc20Token, readTokenRole } from '../contracts.ts' + +/** Parameters for {@link GrantBurnRole}. */ +export type GrantBurnRoleParams = { + /** BurnMintERC677 token (v1.5.1 / v1.6.2) whose roles are being changed. */ + tokenAddress: string + /** Account receiving the burn role; must not already hold it. */ + burner: string + /** Current token owner (the role admin); sets `tx.from` for offline / multisig signing. */ + sender?: string +} + +/** Grants the burn role on a BurnMintERC677 token via `grantBurnRole`. */ +export class GrantBurnRole extends EVMOperation { + readonly name = 'grantBurnRole' + + /** + * Validates both addresses before any RPC. Neither may be zero: a tx to `0x0` hits no code, and + * granting a role to `0x0` mines as a no-op nobody can use. + */ + protected override validate({ tokenAddress, burner }: GrantBurnRoleParams): void { + validateNonZeroAddress(this.name, 'tokenAddress', tokenAddress) + validateNonZeroAddress(this.name, 'burner', burner) + } + + /** + * Reads the current role state, then — when `sender` is known — confirms it owns the token. + * + * The role read runs first because it is also the family check ({@link readTokenRole}), which + * `owner()` cannot make: a token pool and a v2.0.0 `CrossChainToken` declare `owner()` too. Both + * checks run here rather than in {@link execute}, so the offline / multisig path gets them, and + * a redundant grant is rejected even though the chain would mine it as a silent no-op. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * @throws {@link CCTParamsInvalidError} if `burner` already holds the burn role, or `sender` + * is given and is not the token owner + */ + protected async buildUnsigned( + chain: EVMChain, + { tokenAddress, burner, sender }: GrantBurnRoleParams, + ): Promise { + if (await readTokenRole(chain, tokenAddress, 'isBurner', burner)) + throw new CCTParamsInvalidError( + this.name, + 'burner', + `already holds the burn role on ${tokenAddress}; granting it again changes nothing`, + ) + if (sender !== undefined) await assertTokenOwner(this.name, chain, tokenAddress, sender) + + return callTx(tokenAddress, getErc20Token().encodeFunctionData('grantBurnRole', [burner])) + } + + /** + * Signs and submits as the token owner, defaulting `sender` to the signing wallet — the only + * address that can satisfy {@link buildUnsigned}'s owner check for a broadcast tx. See + * {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is rejected. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address, + * or if any other param is invalid (see {@link buildUnsigned}) + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token/operations/grant-mint-and-burn-roles.test.ts b/ccip-sdk/src/cct/evm/token/operations/grant-mint-and-burn-roles.test.ts new file mode 100644 index 000000000..e50a745d3 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/grant-mint-and-burn-roles.test.ts @@ -0,0 +1,277 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { CCTContractTypeInvalidError, CCTParamsInvalidError } from '../../../errors.ts' +import { + type GrantMintAndBurnRolesParams, + GrantMintAndBurnRoles, +} from './grant-mint-and-burn-roles.ts' + +const TOKEN = '0x' + '11'.repeat(20) +const OWNER = '0x' + '22'.repeat(20) +const POOL = '0x' + '33'.repeat(20) +const NOT_THE_OWNER = '0x' + '44'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +/** Calldata built from a fresh Interface — never the SDK's cached one, or this proves nothing. */ +const FRESH = new Interface([ + 'function grantMintAndBurnRoles(address burnAndMinter)', + 'function isMinter(address minter) view returns (bool)', + 'function isBurner(address burner) view returns (bool)', + 'function owner() view returns (address)', +]) +const expectedData = (burnAndMinter = POOL) => + FRESH.encodeFunctionData('grantMintAndBurnRoles', [burnAndMinter]) + +/** The `eth_call`s the op makes, as decoded function names. The two role reads race, so unordered. */ +type Seen = { calls: string[] } +const newSeen = (): Seen => ({ calls: [] }) + +/** + * EVMChain stub for a BurnMintERC677 token owned by `OWNER`, on which `burnAndMinter` already + * holds `roles`. Defaults to holding neither — the fresh-pool case this op exists for. + */ +function stubChain({ + roles = {}, + owner = OWNER, + callError, + seen = newSeen(), +}: { + roles?: { isMinter?: boolean; isBurner?: boolean } + owner?: string + /** Fails every `eth_call`, standing in for a contract that is not a BurnMintERC677 token. */ + callError?: Error + seen?: Seen +} = {}): EVMChain { + const results: Record = { + isMinter: [roles.isMinter ?? false], + isBurner: [roles.isBurner ?? false], + owner: [owner], + } + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + provider: { + call: ({ data }: { data: string }) => { + if (callError) return Promise.reject(callError) + const fn = FRESH.getFunction(data.slice(0, 10))!.name + seen.calls.push(fn) + return Promise.resolve(FRESH.encodeFunctionResult(fn, results[fn])) + }, + }, + nextNonce: () => Promise.resolve(0), + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +function fakeSigner(waitError?: Error, address = OWNER) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => (waitError ? Promise.reject(waitError) : Promise.resolve({ status: 1 })), + }), + } +} + +/** A revert with no data, the shape a call to an undeclared function produces. */ +const missingFunction = () => + makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + +const op = new GrantMintAndBurnRoles() + +function generate(chain: EVMChain, overrides: Partial = {}) { + return op.generate(chain, { + tokenAddress: TOKEN, + burnAndMinter: POOL, + sender: OWNER, + ...overrides, + }) +} + +describe('GrantMintAndBurnRoles (cct/evm)', () => { + describe('generate', () => { + it('encodes grantMintAndBurnRoles(address) to the token', async () => { + const seen = newSeen() + const unsigned = await generate(stubChain({ seen })) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + // one native on-chain function, so one tx — not a grantMintRole + grantBurnRole pair + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, TOKEN) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, expectedData()) + assert.deepEqual(seen.calls.slice(0, 2).sort(), ['isBurner', 'isMinter']) + assert.equal(seen.calls[2], 'owner') + }) + + it('omits from when sender is not supplied, but still probes the token', async () => { + const seen = newSeen() + const unsigned = await generate(stubChain({ seen }), { sender: undefined }) + + assert.equal(unsigned.transactions[0]!.from, undefined) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + // no sender to compare, so the owner read is skipped — the family check is not + assert.deepEqual(seen.calls.sort(), ['isBurner', 'isMinter']) + }) + }) + + describe('validation', () => { + for (const [param, value] of [ + ['tokenAddress', 'not-an-address'], + // a tx to `0x0` hits no code, so it mines as a successful no-op instead of reverting + ['tokenAddress', ZeroAddress], + ['burnAndMinter', 'not-an-address'], + ['burnAndMinter', ZeroAddress], + ['sender', 'not-an-address'], + ] as const) { + it(`rejects ${param} = ${String(value)} before any RPC`, async () => { + const seen = newSeen() + await assert.rejects( + () => generate(stubChain({ seen }), { [param]: value }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'grantMintAndBurnRoles' && + err.context.param === param, + ) + assert.deepEqual(seen.calls, []) + }) + } + }) + + describe('family check', () => { + it('rejects a contract that is not a BurnMintERC677 token', async () => { + // v2.0.0's CrossChainToken declares grantMintAndBurnRoles too, but gates it through + // AccessControl — the isMinter/isBurner reads are what tell the two apart + await assert.rejects( + () => generate(stubChain({ callError: missingFunction() })), + (err: unknown) => + err instanceof CCTContractTypeInvalidError && err.context.address === TOKEN, + ) + }) + + it('rejects an unrelated address even with no sender to check', async () => { + // the case an owner gate alone would miss: a token pool declares owner() too + await assert.rejects( + () => generate(stubChain({ callError: missingFunction() }), { sender: undefined }), + (err: unknown) => err instanceof CCTContractTypeInvalidError, + ) + }) + }) + + describe('no-op guard', () => { + it('rejects an account that already holds both roles', async () => { + // stricter than the chain: the role sets are EnumerableSets, so this would mine as a + // silent no-op rather than revert + const seen = newSeen() + await assert.rejects( + () => generate(stubChain({ roles: { isMinter: true, isBurner: true }, seen })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'grantMintAndBurnRoles' && + err.context.param === 'burnAndMinter' && + /already holds the mint and burn roles/.test(String(err.context.reason)), + ) + // rejected on the role reads alone, before the owner read + assert.ok(!seen.calls.includes('owner')) + }) + + for (const roles of [{ isMinter: true }, { isBurner: true }] as const) { + const held = 'isMinter' in roles ? 'mint' : 'burn' + it(`builds for an account holding only the ${held} role — completing the pair is the point`, async () => { + const unsigned = await generate(stubChain({ roles })) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + }) + } + }) + + describe('owner gate', () => { + it('rejects a sender that does not own the token', async () => { + await assert.rejects( + () => generate(stubChain(), { sender: NOT_THE_OWNER }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'sender' && + /must be the current token owner/.test(String(err.context.reason)), + ) + }) + }) + + describe('execute', () => { + it('submits as the token owner and returns the tx hash', async () => { + const { hash } = await op.execute(stubChain(), { + tokenAddress: TOKEN, + burnAndMinter: POOL, + wallet: fakeSigner(), + }) + assert.equal(hash, HASH) + }) + + it('rejects a sender that is not the signing wallet', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + burnAndMinter: POOL, + sender: NOT_THE_OWNER, + wallet: fakeSigner(), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects a wallet that does not own the token', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + burnAndMinter: POOL, + wallet: fakeSigner(undefined, NOT_THE_OWNER), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('surfaces an on-chain revert', async () => { + const revert = makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'sendTransaction', + data: '0x', + reason: 'OnlyOwner', + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + burnAndMinter: POOL, + wallet: fakeSigner(revert), + }), + (err: unknown) => err instanceof CCIPExecTxRevertedError, + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { tokenAddress: TOKEN, burnAndMinter: POOL, wallet: {} }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token/operations/grant-mint-and-burn-roles.ts b/ccip-sdk/src/cct/evm/token/operations/grant-mint-and-burn-roles.ts new file mode 100644 index 000000000..16865cc69 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/grant-mint-and-burn-roles.ts @@ -0,0 +1,90 @@ +/** + * grantMintAndBurnRoles: grants a BurnMintERC677 token's mint *and* burn roles to one account in + * a single transaction — the call a token owner makes for a newly deployed pool, which needs both. + * Owner-gated (`onlyOwner`); the owner is the token's mint/burn role admin. + * + * @packageDocumentation + */ + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { validateNonZeroAddress } from '../../validate.ts' +import { assertTokenOwner, getErc20Token, readTokenRole } from '../contracts.ts' + +/** Parameters for {@link GrantMintAndBurnRoles}. */ +export type GrantMintAndBurnRolesParams = { + /** BurnMintERC677 token (v1.5.1 / v1.6.2) whose roles are being changed. */ + tokenAddress: string + /** Account receiving both roles, typically the token's pool; must not already hold both. */ + burnAndMinter: string + /** Current token owner (the role admin); sets `tx.from` for offline / multisig signing. */ + sender?: string +} + +/** Grants both mint and burn roles on a BurnMintERC677 token via `grantMintAndBurnRoles`. */ +export class GrantMintAndBurnRoles extends EVMOperation { + readonly name = 'grantMintAndBurnRoles' + + /** + * Validates both addresses before any RPC. Neither may be zero: a tx to `0x0` hits no code, and + * granting roles to `0x0` mines as a no-op nobody can use. + */ + protected override validate({ tokenAddress, burnAndMinter }: GrantMintAndBurnRolesParams): void { + validateNonZeroAddress(this.name, 'tokenAddress', tokenAddress) + validateNonZeroAddress(this.name, 'burnAndMinter', burnAndMinter) + } + + /** + * Reads both role states, then — when `sender` is known — confirms it owns the token. + * Rejected only when the account holds both roles already: holding one still builds, since + * completing the pair is what this call is for. + * + * The role reads run first because they are also the family check ({@link readTokenRole}), which + * `owner()` cannot make: a token pool and a v2.0.0 `CrossChainToken` declare `owner()` too, and + * v2.0.0 declares `grantMintAndBurnRoles` itself. Both checks run here rather than in + * {@link execute}, so the offline / multisig path gets them. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * @throws {@link CCTParamsInvalidError} if `burnAndMinter` already holds both roles, or `sender` + * is given and is not the token owner + */ + protected async buildUnsigned( + chain: EVMChain, + { tokenAddress, burnAndMinter, sender }: GrantMintAndBurnRolesParams, + ): Promise { + const [isMinter, isBurner] = await Promise.all([ + readTokenRole(chain, tokenAddress, 'isMinter', burnAndMinter), + readTokenRole(chain, tokenAddress, 'isBurner', burnAndMinter), + ]) + if (isMinter && isBurner) + throw new CCTParamsInvalidError( + this.name, + 'burnAndMinter', + `already holds the mint and burn roles on ${tokenAddress}; granting them again changes nothing`, + ) + if (sender !== undefined) await assertTokenOwner(this.name, chain, tokenAddress, sender) + + return callTx( + tokenAddress, + getErc20Token().encodeFunctionData('grantMintAndBurnRoles', [burnAndMinter]), + ) + } + + /** + * Signs and submits as the token owner, defaulting `sender` to the signing wallet — the only + * address that can satisfy {@link buildUnsigned}'s owner check for a broadcast tx. See + * {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is rejected. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address, or + * if any other param is invalid (see {@link buildUnsigned}) + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token/operations/grant-mint-role.test.ts b/ccip-sdk/src/cct/evm/token/operations/grant-mint-role.test.ts new file mode 100644 index 000000000..8b045fcac --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/grant-mint-role.test.ts @@ -0,0 +1,260 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { CCTContractTypeInvalidError, CCTParamsInvalidError } from '../../../errors.ts' +import { type GrantMintRoleParams, GrantMintRole } from './grant-mint-role.ts' + +const TOKEN = '0x' + '11'.repeat(20) +const OWNER = '0x' + '22'.repeat(20) +const ACCOUNT = '0x' + '33'.repeat(20) +const NOT_THE_OWNER = '0x' + '44'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +/** Calldata built from a fresh Interface — never the SDK's cached one, or this proves nothing. */ +const FRESH = new Interface([ + 'function grantMintRole(address minter)', + 'function isMinter(address minter) view returns (bool)', + 'function owner() view returns (address)', +]) +const expectedData = (minter = ACCOUNT) => FRESH.encodeFunctionData('grantMintRole', [minter]) + +/** The `eth_call`s the op makes, in order, as decoded function names. */ +type Seen = { calls: string[] } +const newSeen = (): Seen => ({ calls: [] }) + +/** + * EVMChain stub for a BurnMintERC677 token owned by `OWNER`. `holdsRole` is the pre-existing + * role state of the account being granted — false is the state that makes this call a real change. + */ +function stubChain({ + holdsRole = false, + owner = OWNER, + callError, + seen = newSeen(), +}: { + holdsRole?: boolean + owner?: string + /** Fails every `eth_call`, standing in for a contract that is not a BurnMintERC677 token. */ + callError?: Error + seen?: Seen +} = {}): EVMChain { + const results: Record = { isMinter: [holdsRole], owner: [owner] } + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + provider: { + call: ({ data }: { data: string }) => { + if (callError) return Promise.reject(callError) + const fn = FRESH.getFunction(data.slice(0, 10))!.name + seen.calls.push(fn) + return Promise.resolve(FRESH.encodeFunctionResult(fn, results[fn])) + }, + }, + nextNonce: () => Promise.resolve(0), + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +function fakeSigner(waitError?: Error, address = OWNER) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => (waitError ? Promise.reject(waitError) : Promise.resolve({ status: 1 })), + }), + } +} + +/** A revert with no data, the shape a call to an undeclared function produces. */ +const missingFunction = () => + makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + +const op = new GrantMintRole() + +function generate(chain: EVMChain, overrides: Partial = {}) { + return op.generate(chain, { tokenAddress: TOKEN, minter: ACCOUNT, sender: OWNER, ...overrides }) +} + +describe('GrantMintRole (cct/evm)', () => { + describe('generate', () => { + it('encodes grantMintRole(address) to the token', async () => { + const seen = newSeen() + const unsigned = await generate(stubChain({ seen })) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, TOKEN) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, expectedData()) + // the role read comes first: it is also the family check, so it gates the owner read + assert.deepEqual(seen.calls, ['isMinter', 'owner']) + }) + + it('omits from when sender is not supplied, but still probes the token', async () => { + const seen = newSeen() + const unsigned = await generate(stubChain({ seen }), { sender: undefined }) + + assert.equal(unsigned.transactions[0]!.from, undefined) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + // no sender to compare, so the owner read is skipped — the family check is not + assert.deepEqual(seen.calls, ['isMinter']) + }) + }) + + describe('validation', () => { + for (const [param, value] of [ + ['tokenAddress', 'not-an-address'], + // a tx to `0x0` hits no code, so it mines as a successful no-op instead of reverting + ['tokenAddress', ZeroAddress], + ['minter', 'not-an-address'], + ['minter', ZeroAddress], + ['sender', 'not-an-address'], + ] as const) { + it(`rejects ${param} = ${String(value)} before any RPC`, async () => { + const seen = newSeen() + await assert.rejects( + () => generate(stubChain({ seen }), { [param]: value }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'grantMintRole' && + err.context.param === param, + ) + assert.deepEqual(seen.calls, []) + }) + } + }) + + describe('family check', () => { + it('rejects a contract that is not a BurnMintERC677 token', async () => { + // a v2.0.0 CrossChainToken, a token pool, and an EOA all fail the isMinter read + await assert.rejects( + () => generate(stubChain({ callError: missingFunction() })), + (err: unknown) => + err instanceof CCTContractTypeInvalidError && err.context.address === TOKEN, + ) + }) + + it('rejects an unrelated address even with no sender to check', async () => { + // the case an owner gate alone would miss: a token pool declares owner() too + await assert.rejects( + () => generate(stubChain({ callError: missingFunction() }), { sender: undefined }), + (err: unknown) => err instanceof CCTContractTypeInvalidError, + ) + }) + }) + + describe('no-op guard', () => { + it('rejects a grant when the account already holds the mint role', async () => { + // stricter than the chain: the role set is an EnumerableSet, so this would mine as a + // silent no-op rather than revert + const seen = newSeen() + await assert.rejects( + () => generate(stubChain({ holdsRole: true, seen })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'grantMintRole' && + err.context.param === 'minter' && + /already holds the mint role/.test(String(err.context.reason)), + ) + // rejected on the role read alone, before the owner read + assert.deepEqual(seen.calls, ['isMinter']) + }) + + it('rejects the no-op with no sender supplied too', async () => { + await assert.rejects( + () => generate(stubChain({ holdsRole: true }), { sender: undefined }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'minter', + ) + }) + }) + + describe('owner gate', () => { + it('rejects a sender that does not own the token', async () => { + await assert.rejects( + () => generate(stubChain(), { sender: NOT_THE_OWNER }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'sender' && + /must be the current token owner/.test(String(err.context.reason)), + ) + }) + }) + + describe('execute', () => { + it('submits as the token owner and returns the tx hash', async () => { + const { hash } = await op.execute(stubChain(), { + tokenAddress: TOKEN, + minter: ACCOUNT, + wallet: fakeSigner(), + }) + assert.equal(hash, HASH) + }) + + it('rejects a sender that is not the signing wallet', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + minter: ACCOUNT, + sender: NOT_THE_OWNER, + wallet: fakeSigner(), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects a wallet that does not own the token', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + minter: ACCOUNT, + wallet: fakeSigner(undefined, NOT_THE_OWNER), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('surfaces an on-chain revert', async () => { + const revert = makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'sendTransaction', + data: '0x', + reason: 'OnlyOwner', + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + minter: ACCOUNT, + wallet: fakeSigner(revert), + }), + (err: unknown) => err instanceof CCIPExecTxRevertedError, + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { tokenAddress: TOKEN, minter: ACCOUNT, wallet: {} }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token/operations/grant-mint-role.ts b/ccip-sdk/src/cct/evm/token/operations/grant-mint-role.ts new file mode 100644 index 000000000..b31ba7c18 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/grant-mint-role.ts @@ -0,0 +1,80 @@ +/** + * grantMintRole: grants a BurnMintERC677 token's mint role to one account. Owner-gated + * (`onlyOwner`); the owner is the token's mint/burn role admin. + * + * @packageDocumentation + */ + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { validateNonZeroAddress } from '../../validate.ts' +import { assertTokenOwner, getErc20Token, readTokenRole } from '../contracts.ts' + +/** Parameters for {@link GrantMintRole}. */ +export type GrantMintRoleParams = { + /** BurnMintERC677 token (v1.5.1 / v1.6.2) whose roles are being changed. */ + tokenAddress: string + /** Account receiving the mint role; must not already hold it. */ + minter: string + /** Current token owner (the role admin); sets `tx.from` for offline / multisig signing. */ + sender?: string +} + +/** Grants the mint role on a BurnMintERC677 token via `grantMintRole`. */ +export class GrantMintRole extends EVMOperation { + readonly name = 'grantMintRole' + + /** + * Validates both addresses before any RPC. Neither may be zero: a tx to `0x0` hits no code, and + * granting a role to `0x0` mines as a no-op nobody can use. + */ + protected override validate({ tokenAddress, minter }: GrantMintRoleParams): void { + validateNonZeroAddress(this.name, 'tokenAddress', tokenAddress) + validateNonZeroAddress(this.name, 'minter', minter) + } + + /** + * Reads the current role state, then — when `sender` is known — confirms it owns the token. + * + * The role read runs first because it is also the family check ({@link readTokenRole}), which + * `owner()` cannot make: a token pool and a v2.0.0 `CrossChainToken` declare `owner()` too. Both + * checks run here rather than in {@link execute}, so the offline / multisig path gets them, and + * a redundant grant is rejected even though the chain would mine it as a silent no-op. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * @throws {@link CCTParamsInvalidError} if `minter` already holds the mint role, or `sender` + * is given and is not the token owner + */ + protected async buildUnsigned( + chain: EVMChain, + { tokenAddress, minter, sender }: GrantMintRoleParams, + ): Promise { + if (await readTokenRole(chain, tokenAddress, 'isMinter', minter)) + throw new CCTParamsInvalidError( + this.name, + 'minter', + `already holds the mint role on ${tokenAddress}; granting it again changes nothing`, + ) + if (sender !== undefined) await assertTokenOwner(this.name, chain, tokenAddress, sender) + + return callTx(tokenAddress, getErc20Token().encodeFunctionData('grantMintRole', [minter])) + } + + /** + * Signs and submits as the token owner, defaulting `sender` to the signing wallet — the only + * address that can satisfy {@link buildUnsigned}'s owner check for a broadcast tx. See + * {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is rejected. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address, + * or if any other param is invalid (see {@link buildUnsigned}) + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token/operations/is-burner.test.ts b/ccip-sdk/src/cct/evm/token/operations/is-burner.test.ts new file mode 100644 index 000000000..569cefc1f --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/is-burner.test.ts @@ -0,0 +1,94 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import { CCTContractTypeInvalidError, CCTParamsInvalidError } from '../../../errors.ts' +import { IsBurner } from './is-burner.ts' + +const TOKEN = '0x' + '11'.repeat(20) +const ACCOUNT = '0x' + '22'.repeat(20) + +/** Read results from a fresh Interface, never the SDK's cached one. */ +const FRESH = new Interface(['function isBurner(address) view returns (bool)']) + +/** The `eth_call`s the op makes, in order, as decoded function names. */ +type Seen = { calls: string[]; args: string[] } +const newSeen = (): Seen => ({ calls: [], args: [] }) + +/** EVMChain stub answering `isBurner(address)` with `holds`. */ +function stubChain({ + holds = true, + callError, + seen = newSeen(), +}: { + holds?: boolean + /** Fails every `eth_call`, standing in for a contract that is not a BurnMintERC677 token. */ + callError?: Error + seen?: Seen +} = {}): EVMChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + provider: { + call: ({ data }: { data: string }) => { + if (callError) return Promise.reject(callError) + const fn = FRESH.getFunction(data.slice(0, 10))! + seen.calls.push(fn.name) + seen.args.push(FRESH.decodeFunctionData(fn, data)[0] as string) + return Promise.resolve(FRESH.encodeFunctionResult(fn, [holds])) + }, + }, + } as unknown as EVMChain +} + +const op = new IsBurner() + +describe('IsBurner (cct/evm)', () => { + it('answers true for a role holder in one call', async () => { + const seen = newSeen() + const holds = await op.query(stubChain({ seen }), { tokenAddress: TOKEN, account: ACCOUNT }) + + assert.equal(holds, true) + assert.deepEqual(seen.calls, ['isBurner']) + assert.deepEqual(seen.args, [ACCOUNT]) + }) + + it('answers false for an account without the role', async () => { + const chain = stubChain({ holds: false }) + assert.equal(await op.query(chain, { tokenAddress: TOKEN, account: ACCOUNT }), false) + }) + + for (const param of ['tokenAddress', 'account'] as const) { + for (const value of ['not-an-address', ZeroAddress]) { + it(`rejects ${param} = ${value} before any RPC`, async () => { + const seen = newSeen() + const params = { tokenAddress: TOKEN, account: ACCOUNT, [param]: value } + await assert.rejects( + () => op.query(stubChain({ seen }), params), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'isBurner' && + err.context.param === param, + ) + assert.deepEqual(seen.calls, []) + }) + } + } + + it('rejects a contract that does not declare isBurner(address)', async () => { + // a v2.0.0 CrossChainToken gates burning through AccessControl instead + const revert = makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + await assert.rejects( + () => op.query(stubChain({ callError: revert }), { tokenAddress: TOKEN, account: ACCOUNT }), + (err: unknown) => err instanceof CCTContractTypeInvalidError && err.context.address === TOKEN, + ) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token/operations/is-burner.ts b/ccip-sdk/src/cct/evm/token/operations/is-burner.ts new file mode 100644 index 000000000..6dc6029b4 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/is-burner.ts @@ -0,0 +1,49 @@ +/** + * isBurner — whether one account holds a BurnMintERC677 token's burn role. The check a caller + * wants before a burn; enumerating the whole set is `getBurners`. + * + * @packageDocumentation + */ + +import type { EVMChain } from '../../../../evm/index.ts' +import { EVMQuery } from '../../query.ts' +import { validateNonZeroAddress } from '../../validate.ts' +import { readTokenRole } from '../contracts.ts' + +/** Parameters for {@link IsBurner}. */ +export type IsBurnerParams = { + /** BurnMintERC677 token (v1.5.1 / v1.6.2) to read. */ + tokenAddress: string + /** Account to test for the burn role. */ + account: string +} + +/** Result of {@link IsBurner}: whether `account` currently holds the token's burn role. */ +export type IsBurnerResult = boolean + +/** Reads whether an account holds a BurnMintERC677 token's burn role, via `isBurner(address)`. */ +export class IsBurner extends EVMQuery { + readonly name = 'isBurner' + + /** + * Validates both addresses; nothing to convert for {@link read}. + * @remarks `account` is rejected as the zero address for the same reason as in + * {@link IsMinter.prepare}: the call could only ever answer `false`. + * @throws {@link CCTParamsInvalidError} if either address is not a valid, non-zero address + */ + protected prepare(params: IsBurnerParams): IsBurnerParams { + validateNonZeroAddress(this.name, 'tokenAddress', params.tokenAddress) + validateNonZeroAddress(this.name, 'account', params.account) + return params + } + + /** + * Reads the role predicate in a single `eth_call`. + * @remarks No version resolution: `isBurner(address)` is identical at v1.5.1 and v1.6.2, and a + * contract that does not declare it is reported by {@link readTokenRole}. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + */ + protected read(chain: EVMChain, { tokenAddress, account }: IsBurnerParams): Promise { + return readTokenRole(chain, tokenAddress, 'isBurner', account) + } +} diff --git a/ccip-sdk/src/cct/evm/token/operations/is-minter.test.ts b/ccip-sdk/src/cct/evm/token/operations/is-minter.test.ts new file mode 100644 index 000000000..1172b8f11 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/is-minter.test.ts @@ -0,0 +1,94 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import { CCTContractTypeInvalidError, CCTParamsInvalidError } from '../../../errors.ts' +import { IsMinter } from './is-minter.ts' + +const TOKEN = '0x' + '11'.repeat(20) +const ACCOUNT = '0x' + '22'.repeat(20) + +/** Read results from a fresh Interface, never the SDK's cached one. */ +const FRESH = new Interface(['function isMinter(address) view returns (bool)']) + +/** The `eth_call`s the op makes, in order, as decoded function names. */ +type Seen = { calls: string[]; args: string[] } +const newSeen = (): Seen => ({ calls: [], args: [] }) + +/** EVMChain stub answering `isMinter(address)` with `holds`. */ +function stubChain({ + holds = true, + callError, + seen = newSeen(), +}: { + holds?: boolean + /** Fails every `eth_call`, standing in for a contract that is not a BurnMintERC677 token. */ + callError?: Error + seen?: Seen +} = {}): EVMChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + provider: { + call: ({ data }: { data: string }) => { + if (callError) return Promise.reject(callError) + const fn = FRESH.getFunction(data.slice(0, 10))! + seen.calls.push(fn.name) + seen.args.push(FRESH.decodeFunctionData(fn, data)[0] as string) + return Promise.resolve(FRESH.encodeFunctionResult(fn, [holds])) + }, + }, + } as unknown as EVMChain +} + +const op = new IsMinter() + +describe('IsMinter (cct/evm)', () => { + it('answers true for a role holder in one call', async () => { + const seen = newSeen() + const holds = await op.query(stubChain({ seen }), { tokenAddress: TOKEN, account: ACCOUNT }) + + assert.equal(holds, true) + assert.deepEqual(seen.calls, ['isMinter']) + assert.deepEqual(seen.args, [ACCOUNT]) + }) + + it('answers false for an account without the role', async () => { + const chain = stubChain({ holds: false }) + assert.equal(await op.query(chain, { tokenAddress: TOKEN, account: ACCOUNT }), false) + }) + + for (const param of ['tokenAddress', 'account'] as const) { + for (const value of ['not-an-address', ZeroAddress]) { + it(`rejects ${param} = ${value} before any RPC`, async () => { + const seen = newSeen() + const params = { tokenAddress: TOKEN, account: ACCOUNT, [param]: value } + await assert.rejects( + () => op.query(stubChain({ seen }), params), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'isMinter' && + err.context.param === param, + ) + assert.deepEqual(seen.calls, []) + }) + } + } + + it('rejects a contract that does not declare isMinter(address)', async () => { + // a v2.0.0 CrossChainToken gates minting through AccessControl instead + const revert = makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + await assert.rejects( + () => op.query(stubChain({ callError: revert }), { tokenAddress: TOKEN, account: ACCOUNT }), + (err: unknown) => err instanceof CCTContractTypeInvalidError && err.context.address === TOKEN, + ) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token/operations/is-minter.ts b/ccip-sdk/src/cct/evm/token/operations/is-minter.ts new file mode 100644 index 000000000..09081ec28 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/is-minter.ts @@ -0,0 +1,50 @@ +/** + * isMinter — whether one account holds a BurnMintERC677 token's mint role. The check a caller + * wants before a `mint`; enumerating the whole set is `getMinters`. + * + * @packageDocumentation + */ + +import type { EVMChain } from '../../../../evm/index.ts' +import { EVMQuery } from '../../query.ts' +import { validateNonZeroAddress } from '../../validate.ts' +import { readTokenRole } from '../contracts.ts' + +/** Parameters for {@link IsMinter}. */ +export type IsMinterParams = { + /** BurnMintERC677 token (v1.5.1 / v1.6.2) to read. */ + tokenAddress: string + /** Account to test for the mint role. */ + account: string +} + +/** Result of {@link IsMinter}: whether `account` currently holds the token's mint role. */ +export type IsMinterResult = boolean + +/** Reads whether an account holds a BurnMintERC677 token's mint role, via `isMinter(address)`. */ +export class IsMinter extends EVMQuery { + readonly name = 'isMinter' + + /** + * Validates both addresses; nothing to convert for {@link read}. + * @remarks `account` is rejected as the zero address: the token can never grant a role to it, + * so the call could only ever answer `false` — a caller passing it has a bug worth surfacing + * rather than an answer worth an RPC. + * @throws {@link CCTParamsInvalidError} if either address is not a valid, non-zero address + */ + protected prepare(params: IsMinterParams): IsMinterParams { + validateNonZeroAddress(this.name, 'tokenAddress', params.tokenAddress) + validateNonZeroAddress(this.name, 'account', params.account) + return params + } + + /** + * Reads the role predicate in a single `eth_call`. + * @remarks No version resolution: `isMinter(address)` is identical at v1.5.1 and v1.6.2, and a + * contract that does not declare it is reported by {@link readTokenRole}. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + */ + protected read(chain: EVMChain, { tokenAddress, account }: IsMinterParams): Promise { + return readTokenRole(chain, tokenAddress, 'isMinter', account) + } +} diff --git a/ccip-sdk/src/cct/evm/token/operations/mint.test.ts b/ccip-sdk/src/cct/evm/token/operations/mint.test.ts new file mode 100644 index 000000000..e0ee3dabc --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/mint.test.ts @@ -0,0 +1,277 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { CCTContractTypeInvalidError, CCTParamsInvalidError } from '../../../errors.ts' +import { type MintParams, Mint } from './mint.ts' + +const TOKEN = '0x' + '11'.repeat(20) +const MINTER = '0x' + '22'.repeat(20) +const RECIPIENT = '0x' + '33'.repeat(20) +const NOT_A_MINTER = '0x' + '44'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) +const AMOUNT = 1_000000000000000000n + +/** Calldata built from a fresh Interface — never the SDK's cached one, or this proves nothing. */ +const FRESH = new Interface([ + 'function mint(address account, uint256 amount)', + 'function isMinter(address minter) view returns (bool)', +]) +const expectedData = (account = RECIPIENT, amount = AMOUNT) => + FRESH.encodeFunctionData('mint', [account, amount]) + +/** The `eth_call`s the op makes, in order, as decoded function names. */ +type Seen = { calls: string[] } +const newSeen = (): Seen => ({ calls: [] }) + +/** EVMChain stub answering `isMinter` off a fresh Interface. */ +function stubChain({ + isMinter = true, + callError, + seen = newSeen(), +}: { + isMinter?: boolean + /** Fails every `eth_call`, standing in for a contract that is not a BurnMintERC677 token. */ + callError?: Error + seen?: Seen +} = {}): EVMChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + provider: { + call: ({ data }: { data: string }) => { + if (callError) return Promise.reject(callError) + const fn = FRESH.getFunction(data.slice(0, 10))!.name + seen.calls.push(fn) + return Promise.resolve(FRESH.encodeFunctionResult(fn, [isMinter])) + }, + }, + nextNonce: () => Promise.resolve(0), + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +function fakeSigner(waitError?: Error, address = MINTER) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => (waitError ? Promise.reject(waitError) : Promise.resolve({ status: 1 })), + }), + } +} + +const op = new Mint() + +function generate(chain: EVMChain, overrides: Partial = {}) { + return op.generate(chain, { + tokenAddress: TOKEN, + account: RECIPIENT, + amount: AMOUNT, + sender: MINTER, + ...overrides, + }) +} + +describe('Mint (cct/evm)', () => { + describe('generate', () => { + it('encodes mint(address,uint256) to the token', async () => { + const unsigned = await generate(stubChain()) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, TOKEN) + assert.equal(tx.from, MINTER) + assert.equal(tx.data, expectedData()) + }) + + it('encodes the full uint256 range', async () => { + const amount = 2n ** 256n - 1n + const unsigned = await generate(stubChain(), { amount }) + assert.equal(unsigned.transactions[0]!.data, expectedData(RECIPIENT, amount)) + }) + + it('accepts a zero amount, which the token mines as a Transfer of nothing', async () => { + const unsigned = await generate(stubChain(), { amount: 0n }) + assert.equal(unsigned.transactions[0]!.data, expectedData(RECIPIENT, 0n)) + }) + + it('omits from when sender is not supplied, but still probes the token', async () => { + const seen = newSeen() + const unsigned = await generate(stubChain({ seen }), { sender: undefined }) + + assert.equal(unsigned.transactions[0]!.from, undefined) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + // the read doubles as the family check, so it runs with no sender to compare + assert.deepEqual(seen.calls, ['isMinter']) + }) + + it('pre-flights with exactly one isMinter read', async () => { + const seen = newSeen() + await generate(stubChain({ seen })) + assert.deepEqual(seen.calls, ['isMinter']) + }) + }) + + describe('validation', () => { + for (const [param, value] of [ + ['tokenAddress', 'not-an-address'], + // a tx to `0x0` hits no code, so it mines as a successful no-op instead of reverting + ['tokenAddress', ZeroAddress], + ['account', 'not-an-address'], + // the token's own _mint reverts on a zero recipient + ['account', ZeroAddress], + ['amount', 1 as never], + ['amount', -1n], + ['amount', 2n ** 256n], + ['sender', 'not-an-address'], + ] as const) { + it(`rejects ${param} = ${String(value)} before any RPC`, async () => { + const seen = newSeen() + await assert.rejects( + () => generate(stubChain({ seen }), { [param]: value }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'mint' && + err.context.param === param, + ) + assert.deepEqual(seen.calls, []) + }) + } + }) + + describe('family check', () => { + it('rejects a contract that is not a BurnMintERC677 token', async () => { + // a v2.0.0 CrossChainToken, a token pool, and an EOA all fail the isMinter read + const revert = makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + await assert.rejects( + () => generate(stubChain({ callError: revert })), + (err: unknown) => + err instanceof CCTContractTypeInvalidError && err.context.address === TOKEN, + ) + }) + + it('rejects an unrelated address even with no sender to check', async () => { + // the case a role gate alone would miss: a mint tx to codeless address mines as a no-op + const revert = makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + await assert.rejects( + () => generate(stubChain({ callError: revert }), { sender: undefined }), + (err: unknown) => err instanceof CCTContractTypeInvalidError, + ) + }) + }) + + describe('role gate', () => { + it('rejects a sender that does not hold the mint role', async () => { + await assert.rejects( + () => generate(stubChain({ isMinter: false }), { sender: NOT_A_MINTER }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'mint' && + err.context.param === 'sender' && + /must hold the mint role/.test(String(err.context.reason)), + ) + }) + + it('does not gate on the owner — a minter that is not the owner still builds', async () => { + const seen = newSeen() + const unsigned = await generate(stubChain({ seen })) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + assert.ok(!seen.calls.includes('owner'), 'mint is onlyMinter, not onlyOwner') + }) + }) + + describe('execute', () => { + it('submits as the minting wallet and returns the tx hash', async () => { + const { hash } = await op.execute(stubChain(), { + tokenAddress: TOKEN, + account: RECIPIENT, + amount: AMOUNT, + wallet: fakeSigner(), + }) + assert.equal(hash, HASH) + }) + + it('rejects a sender that is not the signing wallet', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + account: RECIPIENT, + amount: AMOUNT, + sender: NOT_A_MINTER, + wallet: fakeSigner(), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects a wallet that does not hold the mint role', async () => { + await assert.rejects( + () => + op.execute(stubChain({ isMinter: false }), { + tokenAddress: TOKEN, + account: RECIPIENT, + amount: AMOUNT, + wallet: fakeSigner(undefined, NOT_A_MINTER), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('surfaces an on-chain revert — e.g. a mint past maxSupply, which is not pre-flighted', async () => { + const revert = makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'sendTransaction', + data: '0x', + reason: 'MaxSupplyExceeded', + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + account: RECIPIENT, + amount: AMOUNT, + wallet: fakeSigner(revert), + }), + (err: unknown) => err instanceof CCIPExecTxRevertedError, + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + account: RECIPIENT, + amount: AMOUNT, + wallet: {}, + }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token/operations/mint.ts b/ccip-sdk/src/cct/evm/token/operations/mint.ts new file mode 100644 index 000000000..11c3c6375 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/mint.ts @@ -0,0 +1,95 @@ +/** + * mint — mints new supply of a BurnMintERC677 token to an account. A role-gated manual mint, for + * seeding liquidity or topping up test supply; the bridge path mints through the pool instead. + * + * @packageDocumentation + */ + +import { ZeroAddress } from 'ethers' + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { validateNonZeroAddress, validateUint256 } from '../../validate.ts' +import { getErc20Token, readTokenRole } from '../contracts.ts' + +/** Parameters for {@link Mint}. */ +export type MintParams = { + /** BurnMintERC677 token (v1.5.1 / v1.6.2) to mint. */ + tokenAddress: string + /** Account credited with the newly minted supply. */ + account: string + /** Amount to mint, in the token's smallest unit (`uint256`). */ + amount: bigint + /** Address holding the token's mint role; sets `tx.from` for offline / multisig signing. */ + sender?: string +} + +/** Mints new supply of a BurnMintERC677 token to an account. Gated on the token's mint role. */ +export class Mint extends EVMOperation { + readonly name = 'mint' + + /** + * Validates the token, recipient and amount before any RPC. + * @remarks `account` is rejected as the zero address, which the token's own `_mint` reverts on + * (`ERC20: mint to the zero address`). A zero `amount` is *not* rejected: it mines successfully + * as a `Transfer` of nothing, and accepting it keeps this op's contract the token's own. + * @throws {@link CCTParamsInvalidError} if any param is invalid + */ + protected override validate({ tokenAddress, account, amount }: MintParams): void { + validateNonZeroAddress(this.name, 'tokenAddress', tokenAddress) + validateNonZeroAddress(this.name, 'account', account) + validateUint256(this.name, 'amount', amount) + } + + /** + * Confirms `sender` holds the token's mint role before encoding. + * + * Gated on `isMinter(sender)`, not `owner()`: `mint` is `onlyMinter`, and the owner is only the + * role admin, who need not hold the role. The read runs even with no `sender` to compare + * (against the zero address, answer discarded) because it is also the family check + * ({@link readTokenRole}) — a `mint` built for an address with no code would otherwise mine + * successfully and mint nothing. It runs here rather than in {@link execute} so the offline / + * multisig path is gated too. A mint past a capped token's `maxSupply` is not pre-flighted. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * @throws {@link CCTParamsInvalidError} if `sender` is given and does not hold the mint role + */ + protected async buildUnsigned( + chain: EVMChain, + { tokenAddress, account, amount, sender }: MintParams, + ): Promise { + const isMinter = await readTokenRole(chain, tokenAddress, 'isMinter', sender ?? ZeroAddress) + if (sender !== undefined && !isMinter) + throw new CCTParamsInvalidError( + this.name, + 'sender', + `must hold the mint role on ${tokenAddress} — grant it with grantMintRole (or grantMintAndBurnRoles) as the token owner`, + ) + + return callTx(tokenAddress, getErc20Token().encodeFunctionData('mint', [account, amount])) + } + + /** + * Signs and submits as a minter, defaulting `sender` to the signing wallet — the only address + * that can satisfy {@link buildUnsigned}'s role check for a broadcast tx. See + * {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is rejected. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address, if + * the wallet does not hold the mint role, or if any other param is invalid (see + * {@link buildUnsigned}) + * @throws {@link CCIPExecTxRevertedError} if the tx reverts on-chain — e.g. the mint would + * exceed the token's `maxSupply`, which is not pre-flighted + * @throws {@link CCTTxFailedError} if submission fails before broadcast + * @throws {@link CCTTxNotConfirmedError} if it is not confirmed in time + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token/operations/revoke-burn-role.test.ts b/ccip-sdk/src/cct/evm/token/operations/revoke-burn-role.test.ts new file mode 100644 index 000000000..72f41574c --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/revoke-burn-role.test.ts @@ -0,0 +1,260 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { CCTContractTypeInvalidError, CCTParamsInvalidError } from '../../../errors.ts' +import { type RevokeBurnRoleParams, RevokeBurnRole } from './revoke-burn-role.ts' + +const TOKEN = '0x' + '11'.repeat(20) +const OWNER = '0x' + '22'.repeat(20) +const ACCOUNT = '0x' + '33'.repeat(20) +const NOT_THE_OWNER = '0x' + '44'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +/** Calldata built from a fresh Interface — never the SDK's cached one, or this proves nothing. */ +const FRESH = new Interface([ + 'function revokeBurnRole(address burner)', + 'function isBurner(address burner) view returns (bool)', + 'function owner() view returns (address)', +]) +const expectedData = (burner = ACCOUNT) => FRESH.encodeFunctionData('revokeBurnRole', [burner]) + +/** The `eth_call`s the op makes, in order, as decoded function names. */ +type Seen = { calls: string[] } +const newSeen = (): Seen => ({ calls: [] }) + +/** + * EVMChain stub for a BurnMintERC677 token owned by `OWNER`. `holdsRole` is the pre-existing + * role state of the account being revokeed — true is the state that makes this call a real change. + */ +function stubChain({ + holdsRole = true, + owner = OWNER, + callError, + seen = newSeen(), +}: { + holdsRole?: boolean + owner?: string + /** Fails every `eth_call`, standing in for a contract that is not a BurnMintERC677 token. */ + callError?: Error + seen?: Seen +} = {}): EVMChain { + const results: Record = { isBurner: [holdsRole], owner: [owner] } + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + provider: { + call: ({ data }: { data: string }) => { + if (callError) return Promise.reject(callError) + const fn = FRESH.getFunction(data.slice(0, 10))!.name + seen.calls.push(fn) + return Promise.resolve(FRESH.encodeFunctionResult(fn, results[fn])) + }, + }, + nextNonce: () => Promise.resolve(0), + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +function fakeSigner(waitError?: Error, address = OWNER) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => (waitError ? Promise.reject(waitError) : Promise.resolve({ status: 1 })), + }), + } +} + +/** A revert with no data, the shape a call to an undeclared function produces. */ +const missingFunction = () => + makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + +const op = new RevokeBurnRole() + +function generate(chain: EVMChain, overrides: Partial = {}) { + return op.generate(chain, { tokenAddress: TOKEN, burner: ACCOUNT, sender: OWNER, ...overrides }) +} + +describe('RevokeBurnRole (cct/evm)', () => { + describe('generate', () => { + it('encodes revokeBurnRole(address) to the token', async () => { + const seen = newSeen() + const unsigned = await generate(stubChain({ seen })) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, TOKEN) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, expectedData()) + // the role read comes first: it is also the family check, so it gates the owner read + assert.deepEqual(seen.calls, ['isBurner', 'owner']) + }) + + it('omits from when sender is not supplied, but still probes the token', async () => { + const seen = newSeen() + const unsigned = await generate(stubChain({ seen }), { sender: undefined }) + + assert.equal(unsigned.transactions[0]!.from, undefined) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + // no sender to compare, so the owner read is skipped — the family check is not + assert.deepEqual(seen.calls, ['isBurner']) + }) + }) + + describe('validation', () => { + for (const [param, value] of [ + ['tokenAddress', 'not-an-address'], + // a tx to `0x0` hits no code, so it mines as a successful no-op instead of reverting + ['tokenAddress', ZeroAddress], + ['burner', 'not-an-address'], + ['burner', ZeroAddress], + ['sender', 'not-an-address'], + ] as const) { + it(`rejects ${param} = ${String(value)} before any RPC`, async () => { + const seen = newSeen() + await assert.rejects( + () => generate(stubChain({ seen }), { [param]: value }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'revokeBurnRole' && + err.context.param === param, + ) + assert.deepEqual(seen.calls, []) + }) + } + }) + + describe('family check', () => { + it('rejects a contract that is not a BurnMintERC677 token', async () => { + // a v2.0.0 CrossChainToken, a token pool, and an EOA all fail the isBurner read + await assert.rejects( + () => generate(stubChain({ callError: missingFunction() })), + (err: unknown) => + err instanceof CCTContractTypeInvalidError && err.context.address === TOKEN, + ) + }) + + it('rejects an unrelated address even with no sender to check', async () => { + // the case an owner gate alone would miss: a token pool declares owner() too + await assert.rejects( + () => generate(stubChain({ callError: missingFunction() }), { sender: undefined }), + (err: unknown) => err instanceof CCTContractTypeInvalidError, + ) + }) + }) + + describe('no-op guard', () => { + it('rejects a revoke when the account does not hold the burn role', async () => { + // stricter than the chain: the role set is an EnumerableSet, so this would mine as a + // silent no-op rather than revert + const seen = newSeen() + await assert.rejects( + () => generate(stubChain({ holdsRole: false, seen })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'revokeBurnRole' && + err.context.param === 'burner' && + /does not hold the burn role/.test(String(err.context.reason)), + ) + // rejected on the role read alone, before the owner read + assert.deepEqual(seen.calls, ['isBurner']) + }) + + it('rejects the no-op with no sender supplied too', async () => { + await assert.rejects( + () => generate(stubChain({ holdsRole: false }), { sender: undefined }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'burner', + ) + }) + }) + + describe('owner gate', () => { + it('rejects a sender that does not own the token', async () => { + await assert.rejects( + () => generate(stubChain(), { sender: NOT_THE_OWNER }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'sender' && + /must be the current token owner/.test(String(err.context.reason)), + ) + }) + }) + + describe('execute', () => { + it('submits as the token owner and returns the tx hash', async () => { + const { hash } = await op.execute(stubChain(), { + tokenAddress: TOKEN, + burner: ACCOUNT, + wallet: fakeSigner(), + }) + assert.equal(hash, HASH) + }) + + it('rejects a sender that is not the signing wallet', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + burner: ACCOUNT, + sender: NOT_THE_OWNER, + wallet: fakeSigner(), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects a wallet that does not own the token', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + burner: ACCOUNT, + wallet: fakeSigner(undefined, NOT_THE_OWNER), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('surfaces an on-chain revert', async () => { + const revert = makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'sendTransaction', + data: '0x', + reason: 'OnlyOwner', + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + burner: ACCOUNT, + wallet: fakeSigner(revert), + }), + (err: unknown) => err instanceof CCIPExecTxRevertedError, + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { tokenAddress: TOKEN, burner: ACCOUNT, wallet: {} }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token/operations/revoke-burn-role.ts b/ccip-sdk/src/cct/evm/token/operations/revoke-burn-role.ts new file mode 100644 index 000000000..4c981693d --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/revoke-burn-role.ts @@ -0,0 +1,80 @@ +/** + * revokeBurnRole: removes a BurnMintERC677 token's burn role from one account. Owner-gated + * (`onlyOwner`); the owner is the token's mint/burn role admin. + * + * @packageDocumentation + */ + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { validateNonZeroAddress } from '../../validate.ts' +import { assertTokenOwner, getErc20Token, readTokenRole } from '../contracts.ts' + +/** Parameters for {@link RevokeBurnRole}. */ +export type RevokeBurnRoleParams = { + /** BurnMintERC677 token (v1.5.1 / v1.6.2) whose roles are being changed. */ + tokenAddress: string + /** Account losing the burn role; must currently hold it. */ + burner: string + /** Current token owner (the role admin); sets `tx.from` for offline / multisig signing. */ + sender?: string +} + +/** Removes the burn role from an account on a BurnMintERC677 token via `revokeBurnRole`. */ +export class RevokeBurnRole extends EVMOperation { + readonly name = 'revokeBurnRole' + + /** + * Validates both addresses before any RPC. Neither may be zero: a tx to `0x0` hits no code, and + * revoking a role from `0x0` mines as a no-op. + */ + protected override validate({ tokenAddress, burner }: RevokeBurnRoleParams): void { + validateNonZeroAddress(this.name, 'tokenAddress', tokenAddress) + validateNonZeroAddress(this.name, 'burner', burner) + } + + /** + * Reads the current role state, then — when `sender` is known — confirms it owns the token. + * + * The role read runs first because it is also the family check ({@link readTokenRole}), which + * `owner()` cannot make: a token pool and a v2.0.0 `CrossChainToken` declare `owner()` too. Both + * checks run here rather than in {@link execute}, so the offline / multisig path gets them, and + * revoking a role never held is rejected even though the chain would mine it as a silent no-op. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * @throws {@link CCTParamsInvalidError} if `burner` does not hold the burn role, or `sender` + * is given and is not the token owner + */ + protected async buildUnsigned( + chain: EVMChain, + { tokenAddress, burner, sender }: RevokeBurnRoleParams, + ): Promise { + if (!(await readTokenRole(chain, tokenAddress, 'isBurner', burner))) + throw new CCTParamsInvalidError( + this.name, + 'burner', + `does not hold the burn role on ${tokenAddress}; revoking it changes nothing`, + ) + if (sender !== undefined) await assertTokenOwner(this.name, chain, tokenAddress, sender) + + return callTx(tokenAddress, getErc20Token().encodeFunctionData('revokeBurnRole', [burner])) + } + + /** + * Signs and submits as the token owner, defaulting `sender` to the signing wallet — the only + * address that can satisfy {@link buildUnsigned}'s owner check for a broadcast tx. See + * {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is rejected. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address, + * or if any other param is invalid (see {@link buildUnsigned}) + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/token/operations/revoke-mint-role.test.ts b/ccip-sdk/src/cct/evm/token/operations/revoke-mint-role.test.ts new file mode 100644 index 000000000..80f72e9e4 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/revoke-mint-role.test.ts @@ -0,0 +1,260 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Interface, ZeroAddress, makeError } from 'ethers' + +import { CCIPExecTxRevertedError, CCIPWalletInvalidError } from '../../../../errors/index.ts' +import type { EVMChain } from '../../../../evm/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { CCTContractTypeInvalidError, CCTParamsInvalidError } from '../../../errors.ts' +import { type RevokeMintRoleParams, RevokeMintRole } from './revoke-mint-role.ts' + +const TOKEN = '0x' + '11'.repeat(20) +const OWNER = '0x' + '22'.repeat(20) +const ACCOUNT = '0x' + '33'.repeat(20) +const NOT_THE_OWNER = '0x' + '44'.repeat(20) +const HASH = '0x' + 'ab'.repeat(32) + +/** Calldata built from a fresh Interface — never the SDK's cached one, or this proves nothing. */ +const FRESH = new Interface([ + 'function revokeMintRole(address minter)', + 'function isMinter(address minter) view returns (bool)', + 'function owner() view returns (address)', +]) +const expectedData = (minter = ACCOUNT) => FRESH.encodeFunctionData('revokeMintRole', [minter]) + +/** The `eth_call`s the op makes, in order, as decoded function names. */ +type Seen = { calls: string[] } +const newSeen = (): Seen => ({ calls: [] }) + +/** + * EVMChain stub for a BurnMintERC677 token owned by `OWNER`. `holdsRole` is the pre-existing + * role state of the account being revokeed — true is the state that makes this call a real change. + */ +function stubChain({ + holdsRole = true, + owner = OWNER, + callError, + seen = newSeen(), +}: { + holdsRole?: boolean + owner?: string + /** Fails every `eth_call`, standing in for a contract that is not a BurnMintERC677 token. */ + callError?: Error + seen?: Seen +} = {}): EVMChain { + const results: Record = { isMinter: [holdsRole], owner: [owner] } + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + provider: { + call: ({ data }: { data: string }) => { + if (callError) return Promise.reject(callError) + const fn = FRESH.getFunction(data.slice(0, 10))!.name + seen.calls.push(fn) + return Promise.resolve(FRESH.encodeFunctionResult(fn, results[fn])) + }, + }, + nextNonce: () => Promise.resolve(0), + rollbackNonce: () => {}, + } as unknown as EVMChain +} + +function fakeSigner(waitError?: Error, address = OWNER) { + return { + signTransaction: () => Promise.resolve('0x'), + getAddress: () => Promise.resolve(address), + populateTransaction: (tx: unknown) => Promise.resolve({ ...(tx as object) }), + sendTransaction: () => + Promise.resolve({ + hash: HASH, + wait: () => (waitError ? Promise.reject(waitError) : Promise.resolve({ status: 1 })), + }), + } +} + +/** A revert with no data, the shape a call to an undeclared function produces. */ +const missingFunction = () => + makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'call', + data: '0x', + reason: null, + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + +const op = new RevokeMintRole() + +function generate(chain: EVMChain, overrides: Partial = {}) { + return op.generate(chain, { tokenAddress: TOKEN, minter: ACCOUNT, sender: OWNER, ...overrides }) +} + +describe('RevokeMintRole (cct/evm)', () => { + describe('generate', () => { + it('encodes revokeMintRole(address) to the token', async () => { + const seen = newSeen() + const unsigned = await generate(stubChain({ seen })) + const tx = unsigned.transactions[0]! + + assert.equal(unsigned.family, ChainFamily.EVM) + assert.equal(unsigned.transactions.length, 1) + assert.equal(tx.to, TOKEN) + assert.equal(tx.from, OWNER) + assert.equal(tx.data, expectedData()) + // the role read comes first: it is also the family check, so it gates the owner read + assert.deepEqual(seen.calls, ['isMinter', 'owner']) + }) + + it('omits from when sender is not supplied, but still probes the token', async () => { + const seen = newSeen() + const unsigned = await generate(stubChain({ seen }), { sender: undefined }) + + assert.equal(unsigned.transactions[0]!.from, undefined) + assert.equal(unsigned.transactions[0]!.data, expectedData()) + // no sender to compare, so the owner read is skipped — the family check is not + assert.deepEqual(seen.calls, ['isMinter']) + }) + }) + + describe('validation', () => { + for (const [param, value] of [ + ['tokenAddress', 'not-an-address'], + // a tx to `0x0` hits no code, so it mines as a successful no-op instead of reverting + ['tokenAddress', ZeroAddress], + ['minter', 'not-an-address'], + ['minter', ZeroAddress], + ['sender', 'not-an-address'], + ] as const) { + it(`rejects ${param} = ${String(value)} before any RPC`, async () => { + const seen = newSeen() + await assert.rejects( + () => generate(stubChain({ seen }), { [param]: value }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'revokeMintRole' && + err.context.param === param, + ) + assert.deepEqual(seen.calls, []) + }) + } + }) + + describe('family check', () => { + it('rejects a contract that is not a BurnMintERC677 token', async () => { + // a v2.0.0 CrossChainToken, a token pool, and an EOA all fail the isMinter read + await assert.rejects( + () => generate(stubChain({ callError: missingFunction() })), + (err: unknown) => + err instanceof CCTContractTypeInvalidError && err.context.address === TOKEN, + ) + }) + + it('rejects an unrelated address even with no sender to check', async () => { + // the case an owner gate alone would miss: a token pool declares owner() too + await assert.rejects( + () => generate(stubChain({ callError: missingFunction() }), { sender: undefined }), + (err: unknown) => err instanceof CCTContractTypeInvalidError, + ) + }) + }) + + describe('no-op guard', () => { + it('rejects a revoke when the account does not hold the mint role', async () => { + // stricter than the chain: the role set is an EnumerableSet, so this would mine as a + // silent no-op rather than revert + const seen = newSeen() + await assert.rejects( + () => generate(stubChain({ holdsRole: false, seen })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'revokeMintRole' && + err.context.param === 'minter' && + /does not hold the mint role/.test(String(err.context.reason)), + ) + // rejected on the role read alone, before the owner read + assert.deepEqual(seen.calls, ['isMinter']) + }) + + it('rejects the no-op with no sender supplied too', async () => { + await assert.rejects( + () => generate(stubChain({ holdsRole: false }), { sender: undefined }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'minter', + ) + }) + }) + + describe('owner gate', () => { + it('rejects a sender that does not own the token', async () => { + await assert.rejects( + () => generate(stubChain(), { sender: NOT_THE_OWNER }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'sender' && + /must be the current token owner/.test(String(err.context.reason)), + ) + }) + }) + + describe('execute', () => { + it('submits as the token owner and returns the tx hash', async () => { + const { hash } = await op.execute(stubChain(), { + tokenAddress: TOKEN, + minter: ACCOUNT, + wallet: fakeSigner(), + }) + assert.equal(hash, HASH) + }) + + it('rejects a sender that is not the signing wallet', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + minter: ACCOUNT, + sender: NOT_THE_OWNER, + wallet: fakeSigner(), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('rejects a wallet that does not own the token', async () => { + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + minter: ACCOUNT, + wallet: fakeSigner(undefined, NOT_THE_OWNER), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'sender', + ) + }) + + it('surfaces an on-chain revert', async () => { + const revert = makeError('execution reverted', 'CALL_EXCEPTION', { + action: 'sendTransaction', + data: '0x', + reason: 'OnlyOwner', + transaction: { to: TOKEN, data: '0x' }, + invocation: null, + revert: null, + }) + await assert.rejects( + () => + op.execute(stubChain(), { + tokenAddress: TOKEN, + minter: ACCOUNT, + wallet: fakeSigner(revert), + }), + (err: unknown) => err instanceof CCIPExecTxRevertedError, + ) + }) + + it('rejects a non-signer wallet', async () => { + await assert.rejects( + () => op.execute(stubChain(), { tokenAddress: TOKEN, minter: ACCOUNT, wallet: {} }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/evm/token/operations/revoke-mint-role.ts b/ccip-sdk/src/cct/evm/token/operations/revoke-mint-role.ts new file mode 100644 index 000000000..152c14398 --- /dev/null +++ b/ccip-sdk/src/cct/evm/token/operations/revoke-mint-role.ts @@ -0,0 +1,80 @@ +/** + * revokeMintRole: removes a BurnMintERC677 token's mint role from one account. Owner-gated + * (`onlyOwner`); the owner is the token's mint/burn role admin. + * + * @packageDocumentation + */ + +import type { EVMChain } from '../../../../evm/index.ts' +import type { UnsignedEVMTx } from '../../../../evm/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { type EVMExecuteParams, EVMOperation, callTx } from '../../operation.ts' +import { validateNonZeroAddress } from '../../validate.ts' +import { assertTokenOwner, getErc20Token, readTokenRole } from '../contracts.ts' + +/** Parameters for {@link RevokeMintRole}. */ +export type RevokeMintRoleParams = { + /** BurnMintERC677 token (v1.5.1 / v1.6.2) whose roles are being changed. */ + tokenAddress: string + /** Account losing the mint role; must currently hold it. */ + minter: string + /** Current token owner (the role admin); sets `tx.from` for offline / multisig signing. */ + sender?: string +} + +/** Removes the mint role from an account on a BurnMintERC677 token via `revokeMintRole`. */ +export class RevokeMintRole extends EVMOperation { + readonly name = 'revokeMintRole' + + /** + * Validates both addresses before any RPC. Neither may be zero: a tx to `0x0` hits no code, and + * revoking a role from `0x0` mines as a no-op. + */ + protected override validate({ tokenAddress, minter }: RevokeMintRoleParams): void { + validateNonZeroAddress(this.name, 'tokenAddress', tokenAddress) + validateNonZeroAddress(this.name, 'minter', minter) + } + + /** + * Reads the current role state, then — when `sender` is known — confirms it owns the token. + * + * The role read runs first because it is also the family check ({@link readTokenRole}), which + * `owner()` cannot make: a token pool and a v2.0.0 `CrossChainToken` declare `owner()` too. Both + * checks run here rather than in {@link execute}, so the offline / multisig path gets them, and + * revoking a role never held is rejected even though the chain would mine it as a silent no-op. + * @throws {@link CCTContractTypeInvalidError} if `tokenAddress` is not a BurnMintERC677 token + * @throws {@link CCTParamsInvalidError} if `minter` does not hold the mint role, or `sender` + * is given and is not the token owner + */ + protected async buildUnsigned( + chain: EVMChain, + { tokenAddress, minter, sender }: RevokeMintRoleParams, + ): Promise { + if (!(await readTokenRole(chain, tokenAddress, 'isMinter', minter))) + throw new CCTParamsInvalidError( + this.name, + 'minter', + `does not hold the mint role on ${tokenAddress}; revoking it changes nothing`, + ) + if (sender !== undefined) await assertTokenOwner(this.name, chain, tokenAddress, sender) + + return callTx(tokenAddress, getErc20Token().encodeFunctionData('revokeMintRole', [minter])) + } + + /** + * Signs and submits as the token owner, defaulting `sender` to the signing wallet — the only + * address that can satisfy {@link buildUnsigned}'s owner check for a broadcast tx. See + * {@link EVMOperation.resolveWalletSender} for why a divergent `sender` is rejected. + * @throws {@link CCIPWalletInvalidError} if `wallet` is not a valid signer + * @throws {@link CCTParamsInvalidError} if `sender` is given and is not the wallet's address, + * or if any other param is invalid (see {@link buildUnsigned}) + */ + override async execute( + chain: EVMChain, + params: EVMExecuteParams, + ): Promise { + const sender = await this.resolveWalletSender(params.wallet, params.sender) + return super.execute(chain, { ...params, sender }) + } +} diff --git a/ccip-sdk/src/cct/evm/validate.ts b/ccip-sdk/src/cct/evm/validate.ts new file mode 100644 index 000000000..c7d110cc5 --- /dev/null +++ b/ccip-sdk/src/cct/evm/validate.ts @@ -0,0 +1,239 @@ +/** + * Generic parameter primitives for EVM CCT ops — one Solidity type or one JS shape each, no domain + * knowledge and no chain access, so every one of them throws {@link CCTParamsInvalidError} before + * the first RPC. Op-specific rules (rate limits, lane shapes) live with their op. + * + * @packageDocumentation + */ + +import { ZeroAddress, getAddress, isAddress } from 'ethers' + +import { CCIPAddressInvalidError } from '../../errors/index.ts' +import { ChainFamily } from '../../networks.ts' +import { CCTParamsInvalidError } from '../errors.ts' + +/** + * Asserts `value` is a valid EVM address, narrowing it to `string` for callers. Links the + * canonical {@link CCIPAddressInvalidError} as the `cause`, keeping the + * {@link operation}/{@link param} context on top. + * @throws {@link CCTParamsInvalidError} if `value` is not a valid address + */ +export function validateAddress( + operation: string, + param: string, + value: unknown, +): asserts value is string { + if (typeof value === 'string' && isAddress(value)) return + throw new CCTParamsInvalidError( + operation, + param, + `must be a valid address, got ${String(value)}`, + { + cause: new CCIPAddressInvalidError(String(value), ChainFamily.EVM), + }, + ) +} + +/** + * Asserts `value` is a valid, non-zero EVM address. + * @remarks Normalises with `getAddress` first: a literal `=== ZeroAddress` misses the ICAP + * spelling, and a tx to `0x0` hits no code, so it mines as a successful no-op. + * @throws {@link CCTParamsInvalidError} if `value` is not a valid address, or is the zero address + */ +export function validateNonZeroAddress(operation: string, param: string, value: unknown): void { + validateAddress(operation, param, value) + if (getAddress(value) === ZeroAddress) + throw new CCTParamsInvalidError(operation, param, 'must not be the zero address') +} + +/** + * Asserts `value` is a non-empty (non-blank) string. + * @throws {@link CCTParamsInvalidError} if `value` is not a non-empty string + */ +export function validateNonEmptyString(operation: string, param: string, value: unknown): void { + if (typeof value === 'string' && value.trim().length > 0) return + throw new CCTParamsInvalidError( + operation, + param, + `must be a non-empty string, got ${String(value)}`, + ) +} + +/** + * Asserts `value` is a boolean, narrowing it for callers. + * @throws {@link CCTParamsInvalidError} if `value` is not a boolean + */ +export function validateBoolean( + operation: string, + param: string, + value: unknown, +): asserts value is boolean { + if (typeof value !== 'boolean') + throw new CCTParamsInvalidError(operation, param, 'must be a boolean') +} + +/** + * Asserts `value` is an integer in `[0, 255]` (a Solidity `uint8`). + * @throws {@link CCTParamsInvalidError} if `value` is not such an integer + */ +export function validateUint8(operation: string, param: string, value: unknown): void { + if (typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 255) return + throw new CCTParamsInvalidError( + operation, + param, + `must be an integer in [0, 255], got ${String(value)}`, + ) +} + +/** + * Shared `uintN` range check: the three widths below differ only in their bound and their message, + * so the comparison itself lives here once. + * @throws {@link CCTParamsInvalidError} if `value` is not a `bigint` in `[0, 2^bits − 1]` + */ +function assertUintBits(operation: string, param: string, value: unknown, bits: number): void { + if (typeof value === 'bigint' && value >= 0n && value <= (1n << BigInt(bits)) - 1n) return + throw new CCTParamsInvalidError( + operation, + param, + `must be a bigint in [0, 2^${bits} − 1], got ${String(value)}`, + ) +} + +/** + * Asserts `value` is a `bigint` in `[0, 2^256 − 1]` (a Solidity `uint256`). + * @throws {@link CCTParamsInvalidError} if `value` is not such a bigint + */ +export function validateUint256(operation: string, param: string, value: unknown): void { + assertUintBits(operation, param, value, 256) +} + +/** + * Asserts `value` is a `bigint` in `[0, 2^128 − 1]` (a Solidity `uint128`), narrowing it to + * `bigint` for callers. + * @throws {@link CCTParamsInvalidError} if `value` is not such a bigint + */ +export function validateUint128( + operation: string, + param: string, + value: unknown, +): asserts value is bigint { + assertUintBits(operation, param, value, 128) +} + +/** + * Asserts `value` is a `bigint` in `[0, 2^64 − 1]` (a Solidity `uint64`), narrowing it to `bigint` + * for callers. The width of a CCIP chain selector, so every `remoteChainSelector` goes through it. + * @throws {@link CCTParamsInvalidError} if `value` is not such a bigint + */ +export function validateUint64( + operation: string, + param: string, + value: unknown, +): asserts value is bigint { + assertUintBits(operation, param, value, 64) +} + +/** + * Parses an optionally `0x`-prefixed hex string of whole, non-empty bytes into the 0x-prefixed + * lower-case form ethers encodes as `bytes`. + * + * @remarks The EVM counterpart of Solana's `parseHexBytes`/`parseNonEmptyHexBytes`, minus their + * byte cap: the values this guards are *remote* addresses carried as `bytes` (a lane's remote + * token or remote pool), and a remote may be Solana or Aptos (32 bytes) as easily as EVM (20), so + * a length ceiling here would only reject valid remotes. Shared by the remote-pool write ops and + * `applyChainUpdates`, which previously each carried their own copy of this parser. + * @param operation - Operation name, for the error context. + * @param param - Param path to blame, e.g. `remotePoolAddress` or `chains[0].remoteTokenAddress`. + * @param value - The caller-supplied value, unvalidated. + * @returns The value as `0x`-prefixed lower-case hex. + * @throws {@link CCTParamsInvalidError} if `value` is not a non-empty whole-byte hex string + */ +export function parseHexBytes(operation: string, param: string, value: unknown): string { + const hex = typeof value === 'string' ? value.replace(/^0x/i, '').toLowerCase() : '' + if (typeof value !== 'string' || !/^(?:[\da-f]{2})+$/.test(hex)) { + throw new CCTParamsInvalidError( + operation, + param, + `must be a non-empty hex string of whole bytes, got ${String(value)}`, + ) + } + return `0x${hex}` +} + +/** + * Parses `value` as a plain object, returned as an indexable record so a caller can validate + * fields one by one before the value has a type. `kind` names the shape in the failure message, + * e.g. `'chain update'` → `must be a chain update`. + * @remarks Arrays and class instances (`Date`, `Map`, …) are objects too, but are not valid + * here: an array would pass field checks only by accident of key naming, and an instance's + * fields live on the prototype, not the record. + * @throws {@link CCTParamsInvalidError} if `value` is not a non-null, non-array plain object + */ +export function parseRecord( + operation: string, + param: string, + value: unknown, + kind: string, +): { [k: string]: unknown } { + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + throw new CCTParamsInvalidError(operation, param, `must be a ${kind}`) + } + return value as { [k: string]: unknown } +} + +/** + * Asserts `value` is a dense array of at least `minLength` entries, narrowing it for callers. + * @remarks Holes are rejected explicitly: `forEach`/`map` skip them, so a sparse array would walk + * past every element check and reach ABI encoding as `null` (blamed as e.g. `chainsToAdd[1]`). + * @throws {@link CCTParamsInvalidError} if `value` is not an array, is shorter than `minLength`, + * or is sparse + */ +export function validateArray( + operation: string, + param: string, + value: unknown, + minLength = 0, +): asserts value is unknown[] { + if (!Array.isArray(value) || value.length < minLength) + throw new CCTParamsInvalidError( + operation, + param, + minLength > 0 ? `must be a non-empty array` : 'must be an array', + ) + for (let i = 0; i < value.length; i++) + if (!(i in value)) + throw new CCTParamsInvalidError( + operation, + `${param}[${i}]`, + 'must not be a hole — the array is sparse, and a missing element cannot be encoded', + ) +} + +/** + * Parses a non-empty list of `bytes` values into 0x-prefixed lower-case hex, rejecting duplicates. + * @remarks Duplicates are compared *after* {@link parseHexBytes} normalisation, so `0xAB` and `ab` + * collide the way a Solidity `bytes` set would. + * @returns The values as 0x-prefixed lower-case hex, in input order. + * @throws {@link CCTParamsInvalidError} if the list is empty, not an array, sparse, or holds an + * invalid or duplicate value + */ +export function parseUniqueHexBytesArray( + operation: string, + param: string, + value: unknown, +): string[] { + validateArray(operation, param, value, 1) + const seen = new Set() + return value.map((entry, i) => { + const hex = parseHexBytes(operation, `${param}[${i}]`, entry) + if (seen.has(hex)) { + throw new CCTParamsInvalidError( + operation, + `${param}[${i}]`, + 'must not duplicate an earlier entry in the same array', + ) + } + seen.add(hex) + return hex + }) +} diff --git a/ccip-sdk/src/cct/operation.ts b/ccip-sdk/src/cct/operation.ts new file mode 100644 index 000000000..425f249c0 --- /dev/null +++ b/ccip-sdk/src/cct/operation.ts @@ -0,0 +1,56 @@ +/** + * Cross-family CCT write contract: the pre-RPC lifecycle (validate → parse) plus the + * generate/execute surface. Mirrors {@link Query} for reads; families bind `Chain` and supply + * `buildUnsigned`/`execute`. + * + * @packageDocumentation + */ + +import type { ChainTransaction } from '../types.ts' + +/** Result of a successful CCT write: the confirmed on-chain tx hash. */ +export type TransactionResult = Pick + +/** + * Execute params for a CCT write: an op's own params plus the signing `wallet`. + * Families extend with submit-time extras (e.g. Solana's `computeUnits`). + */ +export type ExecuteParams

= P & { wallet: unknown } + +/** + * Abstract CCT write operation: build unsigned tx(s) with {@link generate}, or + * sign and submit with {@link execute}. + * + * @remarks {@link parse} is the default pre-RPC hook: use it when the op normalizes, or when a + * validated value must reach `buildUnsigned` already narrowed. Reach for {@link validate} only + * when the op purely rejects and `Parsed = P`. + */ +export abstract class Operation { + /** camelCase id; matches the token-manager facade method and error context. */ + abstract readonly name: string + + /** + * Reject invalid params before any chain RPC. No-op by default: an op that normalizes as it + * checks does that work in {@link parse} instead, and needs no empty stub here. + */ + protected validate(_params: Params): void {} + + /** + * Normalize validated params for the builder — defaults, conversions, derived values. Identity + * by default, so an op that needs no normalization declares nothing. + */ + protected parse(params: Params): Parsed { + // `as Parsed` alone does not narrow: `Parsed` is a default, not a constraint. + return params as unknown as Parsed + } + + /** {@link validate} then {@link parse} — the single pre-RPC step, before any chain access. */ + protected prepare(params: Params): Parsed { + this.validate(params) + return this.parse(params) + } + /** Build unsigned transaction(s); no wallet required. */ + abstract generate(chain: Chain, params: Params): Promise + /** Sign and submit via `params.wallet`; returns once confirmed. */ + abstract execute(chain: Chain, params: ExecuteParams): Promise +} diff --git a/ccip-sdk/src/cct/query.ts b/ccip-sdk/src/cct/query.ts new file mode 100644 index 000000000..4bf275b0b --- /dev/null +++ b/ccip-sdk/src/cct/query.ts @@ -0,0 +1,28 @@ +/** + * Cross-family CCT read contract: {@link Query} wires prepare → read, the read-only counterpart + * of `cct/operation.ts`. Each chain family binds it to its own `Chain` type. + * + * @packageDocumentation + */ + +/** + * Abstract CCT read base. Subclasses supply {@link prepare} and {@link read}; no wallet, no + * calldata, no submit. + * @remarks Validation lives in `prepare`, not a hook of its own: a parser that converts an address + * validates it on the way through, so splitting them would check the same field twice. + */ +export abstract class Query { + /** camelCase id; matches the token-manager facade method and error context. */ + abstract readonly name: string + + /** Validate and normalize params before any chain RPC, without mutating the caller's input. */ + protected abstract prepare(params: Params): Parsed + + /** Read and normalize chain state; runs only after {@link prepare} passes. */ + protected abstract read(chain: Chain, params: Parsed): Promise + + /** Run {@link prepare} and {@link read}; no wallet. */ + async query(chain: Chain, params: Params): Promise { + return this.read(chain, this.prepare(params)) + } +} diff --git a/ccip-sdk/src/cct/solana/index.test.ts b/ccip-sdk/src/cct/solana/index.test.ts new file mode 100644 index 000000000..c575e790d --- /dev/null +++ b/ccip-sdk/src/cct/solana/index.test.ts @@ -0,0 +1,805 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { BorshAccountsCoder } from '@coral-xyz/anchor' +import { TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { Connection, Keypair, PublicKey } from '@solana/web3.js' + +import { SolanaChain } from '../../solana/index.ts' +import { deriveTokenAdminRegistryPda } from './programs/router.ts' +import { + TOKEN_POOL_PROGRAMS, + deriveTokenPoolConfigPda, + deriveTokenPoolSignerPda, +} from './programs/token-pool.ts' +import type { + GetTokenPoolStateParams, + GetTokenPoolStateResult, +} from './token-pool/operations/index.ts' +import { METADATA_PROGRAM_ID } from './token/constants.ts' +import { + type RegisterAdminMethod, + type TokenAuthorityType, + DEFAULT_WRITABLE_INDEXES, + REGISTRATION_METHODS, + SolanaTokenManager, + TOKEN_AUTHORITY_TYPES, +} from './index.ts' + +function stubChain(): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: {}, + } as unknown as SolanaChain +} + +describe('SolanaTokenManager (cct/solana)', () => { + it('exports public CCT constants', () => { + const authorityType: TokenAuthorityType = TOKEN_AUTHORITY_TYPES.MINT + const method: RegisterAdminMethod = REGISTRATION_METHODS.OWNER + + assert.equal(authorityType, 'mint') + assert.equal(TOKEN_AUTHORITY_TYPES.FREEZE, 'freeze') + assert.equal(method, 'owner') + assert.equal(REGISTRATION_METHODS.CCIP_ADMIN, 'ccip-admin') + assert.deepEqual(DEFAULT_WRITABLE_INDEXES, [3, 4, 7]) + }) + + it('creates from a connection provider', async (t) => { + const chain = stubChain() + const connection = new Connection('http://localhost:8899') + t.mock.method(SolanaChain, 'fromConnection', async (provider: Connection) => { + assert.equal(provider, connection) + return chain + }) + + const cct = await SolanaTokenManager.fromProvider(connection) + + assert.equal(cct.chain, chain) + }) + + it('creates from an RPC URL', async (t) => { + const chain = stubChain() + t.mock.method(SolanaChain, 'fromUrl', async (url: string) => { + assert.equal(url, 'http://localhost:8899') + return chain + }) + + const cct = await SolanaTokenManager.fromUrl('http://localhost:8899') + + assert.equal(cct.chain, chain) + }) + + it('getTokenPoolState accepts params whose pool program is not known statically', () => { + const cct = SolanaTokenManager.fromChain(stubChain()) + // A parameter is not narrowed to one PoolProgramRef arm the way a const literal is, so this + // only compiles while a `GetTokenPoolStateParams` overload is declared: TypeScript never + // exposes the implementation signature to callers. + const read = (opts: GetTokenPoolStateParams): Promise => + cct.getTokenPoolState(opts) + + assert.equal(typeof read, 'function') + }) + + describe('facade operations', () => { + const payer = Keypair.generate().publicKey.toBase58() + const mint = Keypair.generate().publicKey.toBase58() + const pool = Keypair.generate().publicKey.toBase58() + const account = Keypair.generate().publicKey.toBase58() + const reader = Keypair.generate().publicKey.toBase58() + const overrideAddress = Keypair.generate().publicKey.toBase58() + const overrideRouter = Keypair.generate().publicKey.toBase58() + const remoteChainSelector = 5009297550715157269n + + function chain(): SolanaChain { + const mintAccount = Buffer.alloc(82) + mintAccount.writeUInt32LE(1, 0) + new PublicKey(payer).toBuffer().copy(mintAccount, 4) + mintAccount[44] = 6 + mintAccount[45] = 1 + const tokenAccount = Buffer.alloc(165) + new PublicKey(mint).toBuffer().copy(tokenAccount, 0) + new PublicKey(payer).toBuffer().copy(tokenAccount, 32) + tokenAccount.writeBigUInt64LE(1n, 64) + tokenAccount.writeUInt32LE(1, 72) + deriveTokenPoolSignerPda( + new PublicKey(TOKEN_POOL_PROGRAMS['lock-release']), + new PublicKey(mint), + ) + .toBuffer() + .copy(tokenAccount, 76) + tokenAccount[108] = 1 + tokenAccount.writeBigUInt64LE(1n, 121) + const poolProgram = new PublicKey(TOKEN_POOL_PROGRAMS['lock-release']) + const poolStateAddress = deriveTokenPoolConfigPda(poolProgram, new PublicKey(mint)) + const registryAddress = deriveTokenAdminRegistryPda( + new PublicKey(account), + new PublicKey(mint), + ) + const readerRegistryAddress = deriveTokenAdminRegistryPda( + new PublicKey(pool), + new PublicKey(mint), + ) + const registry = Buffer.alloc(170) + BorshAccountsCoder.accountDiscriminator('TokenAdminRegistry').copy(registry) + registry[8] = 2 + new PublicKey(payer).toBuffer().copy(registry, 9) + new PublicKey(payer).toBuffer().copy(registry, 41) + new PublicKey(account).toBuffer().copy(registry, 73) + registry[120] = 0x19 + new PublicKey(mint).toBuffer().copy(registry, 137) + registry[169] = 1 + const [metadataAddress] = PublicKey.findProgramAddressSync( + [Buffer.from('metadata'), METADATA_PROGRAM_ID.toBuffer(), new PublicKey(mint).toBuffer()], + METADATA_PROGRAM_ID, + ) + const metadata = Buffer.concat([ + Buffer.from([4]), + new PublicKey(payer).toBuffer(), + new PublicKey(mint).toBuffer(), + Buffer.alloc(14), + Buffer.from([0, 0, 1, 0, 0, 0, 0, 0]), + ]) + const poolState = Buffer.concat([ + BorshAccountsCoder.accountDiscriminator('State'), + Buffer.from([1]), + poolProgram.toBuffer(), + new PublicKey(mint).toBuffer(), + Buffer.from([6]), + ...Array.from({ length: 8 }, () => new PublicKey(payer).toBuffer()), + Buffer.from([1, 1, 0, 0, 0, 0]), + new PublicKey(payer).toBuffer(), + new PublicKey(payer).toBuffer(), + new PublicKey(payer).toBuffer(), + ]) + const accounts = new Map([ + [new PublicKey(mint).toBase58(), { owner: TOKEN_PROGRAM_ID, data: mintAccount }], + [metadataAddress.toBase58(), { owner: METADATA_PROGRAM_ID, data: metadata }], + [poolStateAddress.toBase58(), { owner: poolProgram, data: poolState }], + [registryAddress.toBase58(), null], + [readerRegistryAddress.toBase58(), { data: registry }], + ]) + const defaultAccount = { owner: TOKEN_PROGRAM_ID, data: tokenAccount } + + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + rpcEndpoint: 'http://localhost:8899', + getAccountInfo: async (address: PublicKey) => { + const key = address.toBase58() + return accounts.has(key) ? accounts.get(key) : defaultAccount + }, + getMinimumBalanceForRentExemption: async () => 1, + getSlot: async () => 1, + getAddressLookupTable: async () => ({ + value: { + state: { + authority: new PublicKey(payer), + addresses: [ + PublicKey.default, + PublicKey.default, + PublicKey.default, + new PublicKey(pool), + ], + }, + }, + }), + simulateTransaction: async () => ({ + value: { err: null, logs: [], unitsConsumed: 1 }, + }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => PublicKey.default.toBase58(), + confirmTransaction: async () => ({ value: { err: null } }), + }, + getTokenAdminRegistryFor: async (address: string) => + address === reader ? pool : address === overrideAddress ? overrideRouter : account, + getSupportedTokens: async () => [mint], + getTokenInfo: async () => ({ symbol: 'TKN', decimals: 6 }), + getTokenPoolRemotes: async () => ({}), + getRegistryTokenConfig: async (router: string) => + router === overrideRouter + ? { + administrator: PublicKey.default.toBase58(), + pendingAdministrator: payer, + } + : { administrator: payer, pendingAdministrator: payer }, + } as unknown as SolanaChain + } + + const facadeChain = chain() + + it('runs every unsigned facade operation', async () => { + const cct = SolanaTokenManager.fromChain(facadeChain) + const common = { + payer, + tokenAddress: mint, + authority: payer, + poolType: 'lock-release' as const, + } + const cases: Array< + [string, () => Promise<{ instructions: unknown[] } | { instructions: unknown[] }[]>] + > = [ + [ + 'deployToken', + () => + cct.generateUnsignedDeployToken({ + payer, + decimals: 6, + withMetaplex: false, + }), + ], + [ + 'approveToken', + () => + cct.generateUnsignedApproveToken({ + ...common, + delegate: account, + amount: 1n, + }), + ], + [ + 'createTokenAccount', + () => + cct.generateUnsignedCreateTokenAccount({ + payer, + tokenAddress: mint, + ownerAddress: account, + }), + ], + [ + 'mintTokens', + () => + cct.generateUnsignedMintTokens({ + ...common, + recipient: account, + amount: 1n, + }), + ], + [ + 'setTokenAuthority', + () => + cct.generateUnsignedSetTokenAuthority({ + ...common, + newAuthority: account, + authorityTypes: ['mint'], + }), + ], + [ + 'updateMetadataAuthority', + () => + cct.generateUnsignedUpdateMetadataAuthority({ + ...common, + newAuthority: account, + }), + ], + [ + 'createTokenMultisig', + () => + cct.generateUnsignedCreateTokenMultisig({ + payer, + tokenAddress: mint, + poolType: 'lock-release', + threshold: 1, + }), + ], + [ + 'createLookupTable', + () => + cct.generateUnsignedCreateLookupTable({ + payer, + authority: payer, + mode: 'createEmpty', + }), + ], + [ + 'configureAllowlist', + () => + cct.generateUnsignedConfigureAllowlist({ + ...common, + add: [account], + enabled: true, + }), + ], + ['deployTokenPool', () => cct.generateUnsignedDeployTokenPool(common)], + [ + 'applyChainUpdates', + () => + cct.generateUnsignedApplyChainUpdates({ + ...common, + remoteChainSelectorsToRemove: [], + chainsToAdd: [ + { + remoteChainSelector, + remoteTokenAddress: '0x01', + remotePoolAddresses: ['0x02'], + remoteTokenDecimals: 6, + inboundRateLimiterConfig: { enabled: false }, + outboundRateLimiterConfig: { enabled: false }, + }, + ], + }), + ], + [ + 'appendRemotePoolAddresses', + () => + cct.generateUnsignedAppendRemotePoolAddresses({ + ...common, + remoteChainSelector, + remotePoolAddresses: ['0x01'], + }), + ], + [ + 'initChainRemoteConfig', + () => + cct.generateUnsignedInitChainRemoteConfig({ + ...common, + remoteChainSelector, + remoteTokenAddress: '0x01', + remoteTokenDecimals: 6, + }), + ], + [ + 'deleteChainRemoteConfig', + () => + cct.generateUnsignedDeleteChainRemoteConfig({ + ...common, + remoteChainSelector, + }), + ], + [ + 'setRateLimitAdmin', + () => + cct.generateUnsignedSetRateLimitAdmin({ + ...common, + newRateLimitAdmin: account, + }), + ], + ['provideLiquidity', () => cct.generateUnsignedProvideLiquidity({ ...common, amount: 1n })], + [ + 'withdrawLiquidity', + () => cct.generateUnsignedWithdrawLiquidity({ ...common, amount: 1n }), + ], + [ + 'setCanAcceptLiquidity', + () => + cct.generateUnsignedSetCanAcceptLiquidity({ + ...common, + allow: true, + }), + ], + [ + 'setRebalancer', + () => + cct.generateUnsignedSetRebalancer({ + ...common, + rebalancer: account, + }), + ], + [ + 'transferOwnership', + () => + cct.generateUnsignedTransferOwnership({ + ...common, + newOwner: account, + }), + ], + ['acceptOwnership', () => cct.generateUnsignedAcceptOwnership(common)], + [ + 'setChainRateLimit', + () => + cct.generateUnsignedSetChainRateLimit({ + ...common, + remoteChainSelector, + inbound: { enabled: false }, + outbound: { enabled: false }, + }), + ], + [ + 'editChainRemoteConfig', + () => + cct.generateUnsignedEditChainRemoteConfig({ + ...common, + remoteChainSelector, + remoteTokenAddress: '0x01', + remotePoolAddresses: ['0x02'], + remoteTokenDecimals: 6, + }), + ], + [ + 'appendToLookupTable', + () => + cct.generateUnsignedAppendToLookupTable({ + payer, + lookupTableAddress: account, + additionalAddresses: [mint], + }), + ], + ['acceptAdmin', () => cct.generateUnsignedAcceptAdmin({ ...common, address: account })], + [ + 'ownerOverridePendingAdministrator', + () => + cct.generateUnsignedOwnerOverridePendingAdministrator({ + ...common, + address: overrideAddress, + newAdmin: account, + }), + ], + ['registerAdmin', () => cct.generateUnsignedRegisterAdmin({ ...common, address: account })], + [ + 'removeFromAllowlist', + () => + cct.generateUnsignedRemoveFromAllowlist({ + ...common, + remove: [account], + }), + ], + [ + 'setPool', + () => + cct.generateUnsignedSetPool({ + ...common, + address: account, + poolLookupTableAddress: account, + }), + ], + [ + 'transferAdmin', + () => + cct.generateUnsignedTransferAdmin({ + ...common, + address: account, + newAdmin: account, + }), + ], + ] + + for (const [name, operation] of cases) { + const result = await operation() + assert.ok( + (Array.isArray(result) ? result[0] : result)?.instructions.length, + `${name} returns instructions`, + ) + } + }) + + it('runs every signed facade operation', async () => { + const cct = SolanaTokenManager.fromChain(facadeChain) + const wallet = { + publicKey: new PublicKey(payer), + signTransaction: async (tx: T) => tx, + } + const signed: Array< + [string, () => Promise<{ hash: string } | { hash: string }[] | { hashes: string[] }>] + > = [ + ['deployToken', () => cct.deployToken({ wallet, decimals: 6, withMetaplex: false })], + [ + 'approveToken', + () => + cct.approveToken({ + wallet, + tokenAddress: mint, + delegate: account, + amount: 1n, + }), + ], + [ + 'createTokenAccount', + () => + cct.createTokenAccount({ + wallet, + tokenAddress: mint, + ownerAddress: account, + }), + ], + [ + 'mintTokens', + () => + cct.mintTokens({ + wallet, + tokenAddress: mint, + recipient: account, + amount: 1n, + }), + ], + [ + 'setTokenAuthority', + () => + cct.setTokenAuthority({ + wallet, + tokenAddress: mint, + newAuthority: account, + authorityTypes: ['mint'], + }), + ], + [ + 'updateMetadataAuthority', + () => + cct.updateMetadataAuthority({ + wallet, + tokenAddress: mint, + newAuthority: account, + }), + ], + [ + 'createTokenMultisig', + () => + cct.createTokenMultisig({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + threshold: 1, + }), + ], + ['createLookupTable', () => cct.createLookupTable({ wallet, mode: 'createEmpty' })], + [ + 'configureAllowlist', + () => + cct.configureAllowlist({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + add: [account], + enabled: true, + }), + ], + [ + 'deployTokenPool', + () => + cct.deployTokenPool({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + }), + ], + [ + 'applyChainUpdates', + () => + cct.applyChainUpdates({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + remoteChainSelectorsToRemove: [], + chainsToAdd: [ + { + remoteChainSelector, + remoteTokenAddress: '0x01', + remotePoolAddresses: ['0x02'], + remoteTokenDecimals: 6, + inboundRateLimiterConfig: { enabled: false }, + outboundRateLimiterConfig: { enabled: false }, + }, + ], + }), + ], + [ + 'appendRemotePoolAddresses', + () => + cct.appendRemotePoolAddresses({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + remoteChainSelector, + remotePoolAddresses: ['0x01'], + }), + ], + [ + 'initChainRemoteConfig', + () => + cct.initChainRemoteConfig({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + remoteChainSelector, + remoteTokenAddress: '0x01', + remoteTokenDecimals: 6, + }), + ], + [ + 'deleteChainRemoteConfig', + () => + cct.deleteChainRemoteConfig({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + remoteChainSelector, + }), + ], + [ + 'setRateLimitAdmin', + () => + cct.setRateLimitAdmin({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + newRateLimitAdmin: account, + }), + ], + [ + 'provideLiquidity', + () => + cct.provideLiquidity({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + amount: 1n, + }), + ], + [ + 'withdrawLiquidity', + () => + cct.withdrawLiquidity({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + amount: 1n, + }), + ], + [ + 'setCanAcceptLiquidity', + () => + cct.setCanAcceptLiquidity({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + allow: true, + }), + ], + [ + 'setRebalancer', + () => + cct.setRebalancer({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + rebalancer: account, + }), + ], + [ + 'transferOwnership', + () => + cct.transferOwnership({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + newOwner: account, + }), + ], + [ + 'acceptOwnership', + () => + cct.acceptOwnership({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + }), + ], + [ + 'setChainRateLimit', + () => + cct.setChainRateLimit({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + remoteChainSelector, + inbound: { enabled: false }, + outbound: { enabled: false }, + }), + ], + [ + 'editChainRemoteConfig', + () => + cct.editChainRemoteConfig({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + remoteChainSelector, + remoteTokenAddress: '0x01', + remotePoolAddresses: ['0x02'], + remoteTokenDecimals: 6, + }), + ], + [ + 'appendToLookupTable', + () => + cct.appendToLookupTable({ + wallet, + lookupTableAddress: account, + additionalAddresses: [mint], + }), + ], + ['acceptAdmin', () => cct.acceptAdmin({ wallet, tokenAddress: mint, address: account })], + [ + 'ownerOverridePendingAdministrator', + () => + cct.ownerOverridePendingAdministrator({ + wallet, + tokenAddress: mint, + address: overrideAddress, + newAdmin: account, + }), + ], + [ + 'registerAdmin', + () => cct.registerAdmin({ wallet, tokenAddress: mint, address: account }), + ], + [ + 'removeFromAllowlist', + () => + cct.removeFromAllowlist({ + wallet, + tokenAddress: mint, + poolType: 'lock-release', + remove: [account], + }), + ], + [ + 'setPool', + () => + cct.setPool({ + wallet, + tokenAddress: mint, + address: account, + poolLookupTableAddress: account, + }), + ], + [ + 'transferAdmin', + () => + cct.transferAdmin({ + wallet, + tokenAddress: mint, + address: account, + newAdmin: account, + }), + ], + ] + + for (const [name, operation] of signed) { + const result = await operation() + const hashes = Array.isArray(result) + ? result.map(({ hash }) => hash) + : 'hashes' in result + ? result.hashes + : [result.hash] + assert.ok(hashes.length && hashes.every(Boolean), `${name} returns transaction hashes`) + } + }) + + it('runs every read facade operation', async () => { + const cct = SolanaTokenManager.fromChain(facadeChain) + const reads: Array<[string, () => Promise]> = [ + [ + 'getTokenPoolRemotes', + () => + cct.getTokenPoolRemotes({ + tokenAddress: mint, + poolType: 'lock-release', + remoteChainSelector, + }), + ], + ['getTokenInfo', () => cct.getTokenInfo({ tokenAddress: mint })], + [ + 'getTokenPoolState', + () => + cct.getTokenPoolState({ + tokenAddress: mint, + poolType: 'lock-release', + }), + ], + [ + 'getTokenAdminRegistry', + () => cct.getTokenAdminRegistry({ tokenAddress: mint, address: reader }), + ], + ['getSupportedTokens', () => cct.getSupportedTokens({ address: reader })], + ] + + for (const [name, read] of reads) { + const result = await read() + assert.ok(typeof result === 'object', `${name} returns a result`) + } + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/index.ts b/ccip-sdk/src/cct/solana/index.ts new file mode 100644 index 000000000..a4f634beb --- /dev/null +++ b/ccip-sdk/src/cct/solana/index.ts @@ -0,0 +1,2446 @@ +/** + * Solana Cross-Chain Token (CCT) admin operations. + * + * @packageDocumentation + */ + +import type { Connection } from '@solana/web3.js' + +import type { ChainContext } from '../../chain.ts' +import type { ChainFamily } from '../../networks.ts' +import type { SolanaChain } from '../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../solana/types.ts' +import { TokenManager } from '../token-manager.ts' +import { type SerializedSolanaTxEncoding, serializeUnsignedSolanaTx } from './serialize.ts' +import { + type ExecuteAcceptAdminParams, + type ExecuteAcceptAdminResult, + type ExecuteAppendToLookupTableParams, + type ExecuteAppendToLookupTableResult, + type ExecuteCreateLookupTableParams, + type ExecuteCreateLookupTableResult, + type ExecuteOwnerOverridePendingAdministratorParams, + type ExecuteOwnerOverridePendingAdministratorResult, + type ExecuteRegisterAdminParams, + type ExecuteRegisterAdminResult, + type ExecuteSetPoolParams, + type ExecuteSetPoolResult, + type ExecuteTransferAdminParams, + type ExecuteTransferAdminResult, + type GenerateAcceptAdminParams, + type GenerateAcceptAdminResult, + type GenerateAppendToLookupTableParams, + type GenerateAppendToLookupTableResult, + type GenerateCreateLookupTableParams, + type GenerateCreateLookupTableResult, + type GenerateOwnerOverridePendingAdministratorParams, + type GenerateOwnerOverridePendingAdministratorResult, + type GenerateRegisterAdminParams, + type GenerateRegisterAdminResult, + type GenerateSetPoolParams, + type GenerateSetPoolResult, + type GenerateTransferAdminParams, + type GenerateTransferAdminResult, + type GetSupportedTokensParams, + type GetTokenAdminRegistryParams, + type GetTokenAdminRegistryResult, + AcceptAdmin, + AppendToLookupTable, + CreateLookupTable, + GetSupportedTokens, + GetTokenAdminRegistry, + OwnerOverridePendingAdministrator, + RegisterAdmin, + SetPool, + TransferAdmin, +} from './token-admin-registry/operations/index.ts' +import { + type BaseGetTokenPoolStateResult, + type BurnMintPoolProgramRef, + type CustomPoolProgramRef, + type ExecuteAcceptOwnershipParams, + type ExecuteAcceptOwnershipResult, + type ExecuteAppendRemotePoolAddressesParams, + type ExecuteAppendRemotePoolAddressesResult, + type ExecuteApplyChainUpdatesParams, + type ExecuteApplyChainUpdatesResult, + type ExecuteConfigureAllowlistParams, + type ExecuteConfigureAllowlistResult, + type ExecuteCreateTokenMultisigParams, + type ExecuteCreateTokenMultisigResult, + type ExecuteDeleteChainRemoteConfigParams, + type ExecuteDeleteChainRemoteConfigResult, + type ExecuteDeployTokenPoolParams, + type ExecuteDeployTokenPoolResult, + type ExecuteEditChainRemoteConfigParams, + type ExecuteEditChainRemoteConfigResult, + type ExecuteInitChainRemoteConfigParams, + type ExecuteInitChainRemoteConfigResult, + type ExecuteProvideLiquidityParams, + type ExecuteProvideLiquidityResult, + type ExecuteRemoveFromAllowlistParams, + type ExecuteRemoveFromAllowlistResult, + type ExecuteSetCanAcceptLiquidityParams, + type ExecuteSetCanAcceptLiquidityResult, + type ExecuteSetChainRateLimitParams, + type ExecuteSetChainRateLimitResult, + type ExecuteSetRateLimitAdminParams, + type ExecuteSetRateLimitAdminResult, + type ExecuteSetRebalancerParams, + type ExecuteSetRebalancerResult, + type ExecuteTransferOwnershipParams, + type ExecuteTransferOwnershipResult, + type ExecuteWithdrawLiquidityParams, + type ExecuteWithdrawLiquidityResult, + type GenerateAcceptOwnershipParams, + type GenerateAcceptOwnershipResult, + type GenerateAppendRemotePoolAddressesParams, + type GenerateAppendRemotePoolAddressesResult, + type GenerateApplyChainUpdatesParams, + type GenerateApplyChainUpdatesResult, + type GenerateConfigureAllowlistParams, + type GenerateConfigureAllowlistResult, + type GenerateCreateTokenMultisigParams, + type GenerateCreateTokenMultisigResult, + type GenerateDeleteChainRemoteConfigParams, + type GenerateDeleteChainRemoteConfigResult, + type GenerateDeployTokenPoolParams, + type GenerateDeployTokenPoolResult, + type GenerateEditChainRemoteConfigParams, + type GenerateEditChainRemoteConfigResult, + type GenerateInitChainRemoteConfigParams, + type GenerateInitChainRemoteConfigResult, + type GenerateProvideLiquidityParams, + type GenerateProvideLiquidityResult, + type GenerateRemoveFromAllowlistParams, + type GenerateRemoveFromAllowlistResult, + type GenerateSetCanAcceptLiquidityParams, + type GenerateSetCanAcceptLiquidityResult, + type GenerateSetChainRateLimitParams, + type GenerateSetChainRateLimitResult, + type GenerateSetRateLimitAdminParams, + type GenerateSetRateLimitAdminResult, + type GenerateSetRebalancerParams, + type GenerateSetRebalancerResult, + type GenerateTransferOwnershipParams, + type GenerateTransferOwnershipResult, + type GenerateWithdrawLiquidityParams, + type GenerateWithdrawLiquidityResult, + type GetTokenPoolRemotesParams, + type GetTokenPoolRemotesResult, + type GetTokenPoolStateParams, + type GetTokenPoolStateResult, + type LockReleaseGetTokenPoolStateResult, + type LockReleasePoolProgramRef, + AcceptOwnership, + AppendRemotePoolAddresses, + ApplyChainUpdates, + ConfigureAllowlist, + CreateTokenMultisig, + DeleteChainRemoteConfig, + DeployTokenPool, + EditChainRemoteConfig, + GetTokenPoolRemotes, + GetTokenPoolState, + InitChainRemoteConfig, + ProvideLiquidity, + RemoveFromAllowlist, + SetCanAcceptLiquidity, + SetChainRateLimit, + SetRateLimitAdmin, + SetRebalancer, + TransferOwnership, + WithdrawLiquidity, +} from './token-pool/operations/index.ts' +import { + type ExecuteApproveTokenParams, + type ExecuteApproveTokenResult, + type ExecuteCreateTokenAccountParams, + type ExecuteCreateTokenAccountResult, + type ExecuteDeployTokenParams, + type ExecuteDeployTokenResult, + type ExecuteMintTokensParams, + type ExecuteMintTokensResult, + type ExecuteSetTokenAuthorityParams, + type ExecuteSetTokenAuthorityResult, + type ExecuteUpdateMetadataAuthorityParams, + type ExecuteUpdateMetadataAuthorityResult, + type GenerateApproveTokenParams, + type GenerateApproveTokenResult, + type GenerateCreateTokenAccountParams, + type GenerateCreateTokenAccountResult, + type GenerateDeployTokenParams, + type GenerateDeployTokenResult, + type GenerateMintTokensParams, + type GenerateMintTokensResult, + type GenerateSetTokenAuthorityParams, + type GenerateSetTokenAuthorityResult, + type GenerateUpdateMetadataAuthorityParams, + type GenerateUpdateMetadataAuthorityResult, + type GetTokenInfoParams, + type GetTokenInfoResult, + ApproveToken, + CreateTokenAccount, + GetTokenInfo, + MintTokens, + SetTokenAuthority, + UpdateMetadataAuthority, +} from './token/operations/index.ts' + +/** CCT admin facade for Solana. */ +export class SolanaTokenManager extends TokenManager { + readonly chain: SolanaChain + // Token operations + readonly #approveToken = new ApproveToken() + readonly #createTokenAccount = new CreateTokenAccount() + readonly #getTokenInfo = new GetTokenInfo() + readonly #mintTokens = new MintTokens() + readonly #setTokenAuthority = new SetTokenAuthority() + readonly #updateMetadataAuthority = new UpdateMetadataAuthority() + + // Token admin registry operations + readonly #acceptAdmin = new AcceptAdmin() + readonly #appendToLookupTable = new AppendToLookupTable() + readonly #createLookupTable = new CreateLookupTable() + readonly #getSupportedTokens = new GetSupportedTokens() + readonly #getTokenAdminRegistry = new GetTokenAdminRegistry() + readonly #ownerOverridePendingAdministrator = new OwnerOverridePendingAdministrator() + readonly #registerAdmin = new RegisterAdmin() + readonly #setPool = new SetPool() + readonly #transferAdmin = new TransferAdmin() + + // Token pool operations + readonly #acceptOwnership = new AcceptOwnership() + readonly #appendRemotePoolAddresses = new AppendRemotePoolAddresses() + readonly #applyChainUpdates = new ApplyChainUpdates() + readonly #configureAllowlist = new ConfigureAllowlist() + readonly #createTokenMultisig = new CreateTokenMultisig() + readonly #deployTokenPool = new DeployTokenPool() + readonly #deleteChainRemoteConfig = new DeleteChainRemoteConfig() + readonly #editChainRemoteConfig = new EditChainRemoteConfig() + readonly #getTokenPoolRemotes = new GetTokenPoolRemotes() + readonly #getTokenPoolState = new GetTokenPoolState() + readonly #initChainRemoteConfig = new InitChainRemoteConfig() + readonly #provideLiquidity = new ProvideLiquidity() + readonly #removeFromAllowlist = new RemoveFromAllowlist() + readonly #setCanAcceptLiquidity = new SetCanAcceptLiquidity() + readonly #setChainRateLimit = new SetChainRateLimit() + readonly #setRateLimitAdmin = new SetRateLimitAdmin() + readonly #setRebalancer = new SetRebalancer() + readonly #transferOwnership = new TransferOwnership() + readonly #withdrawLiquidity = new WithdrawLiquidity() + + /** Creates a Solana CCT manager for an existing chain. */ + constructor(chain: SolanaChain) { + super() + this.chain = chain + } + + /** Wraps an existing {@link SolanaChain}. */ + static fromChain(chain: SolanaChain): SolanaTokenManager { + return new SolanaTokenManager(chain) + } + + /** Creates from a Solana web3.js connection. */ + static async fromProvider(provider: Connection, ctx?: ChainContext): Promise { + const { SolanaChain } = await import('../../solana/index.ts') + return new SolanaTokenManager(await SolanaChain.fromConnection(provider, ctx)) + } + + /** Creates from an RPC URL. */ + static async fromUrl(url: string, ctx?: ChainContext): Promise { + const { SolanaChain } = await import('../../solana/index.ts') + return new SolanaTokenManager(await SolanaChain.fromUrl(url, ctx)) + } + + /** Provider of the underlying chain. */ + get provider(): Connection { + return this.chain.connection + } + + /** + * Builds unsigned Solana mint creation instructions, optionally with initial supply. + * The `payer` defaults as mint, freeze, and metadata update authority. + * + * @see {@link updateMetadataAuthority} To transfer the initial metadata update authority. + * + * @throws {@link CCTParamsInvalidError} If token parameters are invalid. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedDeployToken({ + * payer, + * decimals: 9, + * tokenProgram: 'spl-token', + * withMetaplex: true, + * name: 'My Token', + * symbol: 'MTK', + * }) + * ``` + */ + async generateUnsignedDeployToken( + opts: GenerateDeployTokenParams, + ): Promise { + const { DeployToken } = await import('./token/operations/index.ts') + return new DeployToken().generate(this.chain, opts) + } + + /** + * Creates a Solana mint, optionally with initial supply. + * The wallet public key defaults as mint, freeze, and metadata update authority. + * + * @see {@link updateMetadataAuthority} To transfer the initial metadata update authority. + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If token parameters are invalid. + * @throws {@link CCTTxFailedError} If transaction simulation or submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.deployToken({ + * wallet, + * decimals: 9, + * tokenProgram: 'spl-token', + * withMetaplex: false, + * }) + * ``` + */ + async deployToken(opts: ExecuteDeployTokenParams): Promise { + const { DeployToken } = await import('./token/operations/index.ts') + return new DeployToken().execute(this.chain, opts) + } + + /** + * Builds unsigned instructions to approve a delegate to transfer SPL tokens. + * + * @see {@link approveToken} For wallet-based execution. + * + * @remarks + * This is a prerequisite for pool liquidity operations: approve the pool signer PDA as `delegate` + * with the maximum allowance it may transfer during `provideLiquidity`. Approval grants a trusted + * delegate spend authority and replaces the account's existing delegate and allowance; set `amount` + * to `0n` to clear the allowance. `tokenAccount` defaults to the authority's existing associated token + * account. For an SPL Token multisig authority, provide `multisigSigners` and collect member signatures + * externally. + * + * @throws {@link CCTParamsInvalidError} If an address, allowance, or multisig signer is invalid. + * @throws {@link CCIPTokenAccountNotFoundError} If the token account does not exist. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedApproveToken({ + * payer: owner, + * tokenAddress: mint, + * delegate, + * amount: 1_000_000n, + * }) + * ``` + */ + generateUnsignedApproveToken( + opts: GenerateApproveTokenParams, + ): Promise { + return this.#approveToken.generate(this.chain, opts) + } + + /** + * Approves a delegate to transfer SPL tokens from the selected token account using the executing + * authority wallet. + * + * @see {@link generateUnsignedApproveToken} For externally signed transactions. + * + * @remarks + * This is a prerequisite for pool liquidity operations: approve the pool signer PDA as `delegate` + * with the maximum allowance it may transfer during `provideLiquidity`. Approval grants a trusted + * delegate spend authority and replaces the account's existing delegate and allowance; set `amount` + * to `0n` to clear the allowance. `tokenAccount` defaults to the authority's existing associated token + * account. SPL Token multisig authorities require `multisigSigners`. + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If an address, allowance, or multisig signer is invalid, or + * `authority` does not match the executing wallet. + * @throws {@link CCIPTokenAccountNotFoundError} If the token account does not exist. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * @throws {@link CCTTxFailedError} If simulation or the SPL Token program rejects the transaction. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.approveToken({ wallet, tokenAddress: mint, delegate, amount: 1_000_000n }) + * ``` + */ + approveToken(opts: ExecuteApproveTokenParams): Promise { + return this.#approveToken.execute(this.chain, opts) + } + + /** + * Builds an unsigned idempotent associated token account create instruction. + * + * @remarks + * This operation is idempotent and safe to re-run. For the canonical pool setup flow, pass the + * `poolSignerAddress` returned by `generateUnsignedDeployTokenPool` as `ownerAddress`, then call + * `generateUnsignedSetPool`. + * + * @see {@link generateUnsignedDeployTokenPool} + * @see {@link generateUnsignedSetPool} + * + * @throws {@link CCTParamsInvalidError} If an address is invalid. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedCreateTokenAccount({ + * payer, + * tokenAddress: mint, + * ownerAddress: owner, + * }) + * ``` + */ + generateUnsignedCreateTokenAccount( + opts: GenerateCreateTokenAccountParams, + ): Promise { + return this.#createTokenAccount.generate(this.chain, opts) + } + + /** + * Creates an associated token account for a wallet or PDA owner. + * + * @remarks + * This operation is idempotent and safe to re-run. For the canonical pool setup flow, pass the + * `poolSignerAddress` returned by `deployTokenPool` as `ownerAddress`, then call `setPool`. + * + * @see {@link deployTokenPool} + * @see {@link setPool} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If an address is invalid. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * @throws {@link CCTTxFailedError} If transaction simulation or submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.createTokenAccount({ wallet, tokenAddress: mint, ownerAddress: owner }) + * ``` + */ + createTokenAccount( + opts: ExecuteCreateTokenAccountParams, + ): Promise { + return this.#createTokenAccount.execute(this.chain, opts) + } + + /** + * Builds unsigned instructions to mint SPL tokens to a recipient's associated token account. + * + * @remarks + * `amount` is in base units. Set `createRecipientATA` to create the recipient ATA idempotently + * before minting; otherwise it must already exist. `authority` defaults to `payer`. For an SPL + * Token multisig authority, provide `multisigSigners` and collect member signatures externally. + * + * @throws {@link CCTParamsInvalidError} If an address, amount, or multisig signer is invalid. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * @throws {@link CCIPTokenAccountNotFoundError} If the recipient ATA is missing and + * `createRecipientATA` is not set. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedMintTokens({ + * payer: mintAuthority, + * tokenAddress: mint, + * recipient, + * amount: 1_000_000n, // One token for a mint with six decimals + * }) + * ``` + */ + generateUnsignedMintTokens(opts: GenerateMintTokensParams): Promise { + return this.#mintTokens.generate(this.chain, opts) + } + + /** + * Mints SPL tokens to a recipient's associated token account using the executing wallet. + * + * @remarks + * `amount` is in base units. Set `createRecipientATA` to create the recipient ATA idempotently + * before minting; otherwise it must already exist. SPL Token multisig authorities require + * `multisigSigners` and external member signatures; use {@link generateUnsignedMintTokens}. + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If an address, amount, or multisig signer is invalid, or + * `authority` does not match the executing wallet. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * @throws {@link CCIPTokenAccountNotFoundError} If the recipient ATA is missing and + * `createRecipientATA` is not set. + * @throws {@link CCTTxFailedError} If simulation or the SPL Token program rejects the transaction. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.mintTokens({ + * wallet, + * tokenAddress: mint, + * recipient, + * amount: 1_000_000n, // One token for a mint with six decimals + * }) + * ``` + */ + mintTokens(opts: ExecuteMintTokensParams): Promise { + return this.#mintTokens.execute(this.chain, opts) + } + + /** + * Builds unsigned instructions for an immediate SPL Token mint and/or freeze authority update. + * + * @see {@link setTokenAuthority} For wallet-based execution. + * + * @remarks + * ⚠️ **IRREVERSIBLE:** Setting `newAuthority` to null **permanently revokes** the selected authority + * roles for the SPL Token. Once revoked, the authority cannot be recovered or transferred. + * Example: revoked mint authority prevents anyone from minting tokens. Use with extreme caution. + * + * Once confirmed, the current authority loses the selected roles. Set `authorityTypes` to + * `['mint']`, `['freeze']`, or both. All selected roles must have the same current authority. The + * instructions are atomic: no role changes if any selected update fails. + * `authority` defaults to `payer`. For an SPL Token multisig authority, provide `multisigSigners` + * and collect member signatures externally. + * + * @throws {@link CCTParamsInvalidError} If an address or authority role selection is invalid. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedSetTokenAuthority({ + * payer: currentAuthority, + * tokenAddress: mint, + * newAuthority, + * authorityTypes: ['mint'], + * }) + * ``` + * + * @example Permanently revoke mint authority + * ```ts + * const revokeUnsigned = await cct.generateUnsignedSetTokenAuthority({ + * payer: currentAuthority, + * tokenAddress: mint, + * newAuthority: null, // ⚠️ PERMANENT + * authorityTypes: ['mint'], + * }) + * ``` + */ + generateUnsignedSetTokenAuthority( + opts: GenerateSetTokenAuthorityParams, + ): Promise { + return this.#setTokenAuthority.generate(this.chain, opts) + } + + /** + * Immediately sets SPL Token mint and/or freeze authority using the executing wallet. + * + * @see {@link generateUnsignedSetTokenAuthority} For externally signed transactions. + * + * @remarks + * ⚠️ **IRREVERSIBLE:** Setting `newAuthority` to null **permanently revokes** the selected authority + * roles for the SPL Token. Once revoked, the authority cannot be recovered or transferred. + * Example: revoked mint authority prevents anyone from minting tokens. Use with extreme caution. + * + * Once confirmed, the current authority loses the selected roles. Set `authorityTypes` to + * `['mint']`, `['freeze']`, or both. All selected roles must have the same current authority. The + * transaction is atomic: no role changes if any selected update fails. + * SPL Token multisig authorities require `multisigSigners` and external member signatures; use + * {@link generateUnsignedSetTokenAuthority}. + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If an address or authority role selection is invalid, or + * `authority` does not match the executing wallet. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * @throws {@link CCTTxFailedError} If simulation or the SPL Token program rejects the transaction. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.setTokenAuthority({ wallet, tokenAddress: mint, newAuthority, authorityTypes: ['mint'] }) + * ``` + */ + setTokenAuthority(opts: ExecuteSetTokenAuthorityParams): Promise { + return this.#setTokenAuthority.execute(this.chain, opts) + } + + /** + * Builds unsigned instructions to transfer a token's Metaplex metadata update authority. + * + * @see {@link updateMetadataAuthority} For wallet-based execution. + * @see {@link setTokenAuthority} For SPL mint and freeze authority changes. + * @see {@link deployToken} To set the initial metadata update authority. + * + * @remarks + * The mint must have mutable Metaplex Token Metadata and `authority` must match its current + * update authority. `authority` defaults to `payer`; both the payer and authority must sign if + * they differ. Use this to hand metadata control to a multisig or DAO after deployment. + * + * @throws {@link CCTParamsInvalidError} If an address is invalid, the mint has no Metaplex + * metadata, or `authority` is not its current metadata update authority. + * @throws {@link CCTTxFailedError} If the metadata is immutable. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedUpdateMetadataAuthority({ + * payer: currentAuthority, + * tokenAddress: mint, + * newAuthority, + * }) + * ``` + */ + generateUnsignedUpdateMetadataAuthority( + opts: GenerateUpdateMetadataAuthorityParams, + ): Promise { + return this.#updateMetadataAuthority.generate(this.chain, opts) + } + + /** + * Transfers a token's Metaplex metadata update authority using the executing wallet. + * + * @see {@link generateUnsignedUpdateMetadataAuthority} For externally signed transactions. + * @see {@link setTokenAuthority} For SPL mint and freeze authority changes. + * @see {@link deployToken} To set the initial metadata update authority. + * + * @remarks + * The mint must have mutable Metaplex Token Metadata and the executing wallet must be its + * current update authority. Use this to hand metadata control to a multisig or DAO after + * deployment. Use {@link generateUnsignedUpdateMetadataAuthority} when payer and authority + * differ or external signatures are required. + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If an address is invalid, the mint has no Metaplex + * metadata, or `authority` does not match the metadata or executing wallet. + * @throws {@link CCTTxFailedError} If the metadata is immutable, simulation fails, or the Metaplex + * program rejects the transaction. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.updateMetadataAuthority({ wallet, tokenAddress: mint, newAuthority }) + * ``` + */ + updateMetadataAuthority( + opts: ExecuteUpdateMetadataAuthorityParams, + ): Promise { + return this.#updateMetadataAuthority.execute(this.chain, opts) + } + + /** + * Builds unsigned SPL Token multisig creation instructions. + * The pool signer PDA occupies `threshold` slots; non-pool signers must meet the threshold independently. + * + * @remarks When `payer` differs from the mint authority, both must sign: the mint authority is + * the `createAccountWithSeed` base account. + * + * @throws {@link CCTParamsInvalidError} If multisig parameters are invalid or the mint has no authority. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedCreateTokenMultisig({ + * payer, + * tokenAddress: mint, + * poolType: 'burn-mint', + * threshold: 2, + * additionalSigners: [admin], + * }) + * ``` + */ + generateUnsignedCreateTokenMultisig( + opts: GenerateCreateTokenMultisigParams, + ): Promise { + return this.#createTokenMultisig.generate(this.chain, opts) + } + + /** + * Creates an SPL Token multisig account. + * The pool signer PDA occupies `threshold` slots; non-pool signers must meet the threshold independently. + * Wallet pays fees and must match the mint authority. + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If multisig parameters are invalid or the wallet is not the mint authority. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * @throws {@link CCTTxFailedError} If transaction simulation or submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const { hash, multisigAddress } = await cct.createTokenMultisig({ + * wallet, + * tokenAddress: mint, + * poolType: 'burn-mint', + * threshold: 2, + * additionalSigners: [admin], + * }) + * ``` + */ + createTokenMultisig( + opts: ExecuteCreateTokenMultisigParams, + ): Promise { + return this.#createTokenMultisig.execute(this.chain, opts) + } + + /** + * Builds unsigned Solana pool lookup table instructions. + * + * Defaults to create+extend. Specify a canonical `poolType` or custom `poolProgramAddress`. + * Use `mode: 'createEmpty'` to create an empty ALT, e.g. with an EOA payer and vault authority, + * then populate it later through the authority. If `authority` is omitted, it defaults to `payer`. + * + * @throws {@link CCTParamsInvalidError} If an address or lookup table parameter is invalid. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedCreateLookupTable({ + * mode: 'createEmpty', + * payer: eoa, + * authority: squadsVault, + * }) + * ``` + */ + generateUnsignedCreateLookupTable( + opts: GenerateCreateLookupTableParams, + ): Promise { + return this.#createLookupTable.generate(this.chain, opts) + } + + /** + * Creates a Solana pool lookup table. Defaults to create+extend; pass `mode: 'createEmpty'` to + * create an empty ALT owned by `authority` and paid by `wallet`. If `authority` is omitted, it + * defaults to the wallet public key. + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If an address or lookup table parameter is invalid. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * @throws {@link CCTTxFailedError} If transaction simulation or submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const { hash, lookupTableAddress } = await cct.createLookupTable({ + * mode: 'createEmpty', + * authority: squadsVault, + * wallet, + * }) + * ``` + */ + createLookupTable(opts: ExecuteCreateLookupTableParams): Promise { + return this.#createLookupTable.execute(this.chain, opts) + } + + /** + * Builds an unsigned instruction to append addresses to a token pool allowlist and toggle + * enforcement. Every call overwrites enforcement; pass `add: []` to toggle it without appending + * an address. Addresses in `add` must be unique; existing allowlist entries are rejected by the + * program. The pool must be initialized first. Pass canonical `poolType` or a compatible + * `poolProgramAddress`; `authority` defaults to `payer`. + * + * @see {@link configureAllowlist} + * @see {@link generateUnsignedDeployTokenPool} + * @see {@link generateUnsignedRemoveFromAllowlist} + * + * @throws {@link CCTParamsInvalidError} If a pool parameter is invalid. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedConfigureAllowlist({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * add: [allowedSender], + * enabled: true, + * payer, + * authority, + * }) + * ``` + */ + generateUnsignedConfigureAllowlist( + opts: GenerateConfigureAllowlistParams, + ): Promise { + return this.#configureAllowlist.generate(this.chain, opts) + } + + /** + * Appends addresses to and configures an initialized Solana token pool allowlist using the pool + * owner wallet. Every call overwrites enforcement; pass `add: []` to toggle it without + * appending an address. Addresses in `add` must be unique; existing allowlist entries are + * rejected by the program. + * + * @see {@link generateUnsignedConfigureAllowlist} + * @see {@link deployTokenPool} + * @see {@link removeFromAllowlist} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If a pool parameter is invalid or the authority differs + * from the executing wallet. + * @throws {@link CCTTxFailedError} If transaction simulation or submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.configureAllowlist({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * add: [], + * enabled: false, + * wallet, + * }) + * ``` + */ + configureAllowlist( + opts: ExecuteConfigureAllowlistParams, + ): Promise { + return this.#configureAllowlist.execute(this.chain, opts) + } + + /** + * Builds unsigned Solana token pool initialize instructions. + * + * @remarks + * This only builds the pool `initialize` instruction for the canonical `burn-mint` and + * `lock-release` programs selected by `poolType`; custom pool deployment is unsupported. `authority` + * must be allowed to initialize the pool. + * + * **Important:** The pool requires a `pool_token_account` (the pool signer PDA's associated token + * account) to lock/release or mint on transfers. Set `createPoolSignerATA: true` to create it + * idempotently in this transaction. If omitted (defaults to `false`), create it separately with + * the returned `poolSignerAddress` via `generateUnsignedCreateTokenAccount` before + * `generateUnsignedSetPool`, or transfers fail with `AccountNotInitialized (3012)`. + * + * When to use `createPoolSignerATA: true` vs. the separate + * `generateUnsignedCreateTokenAccount` op: + * - Use this option when deploying a pool that will immediately receive transfers (simplest, one tx) + * - Use the separate op for vault-owned pools or when decoupling pool initialization from ATA setup + * + * This option is Solana-only (no EVM equivalent). It is analogous to `createRecipientATA` on + * {@link mintTokens}, the same idiomatic pattern for atomicity. + * + * @see {@link generateUnsignedCreateTokenAccount} + * @see {@link generateUnsignedSetPool} + * @see {@link mintTokens} + * + * @throws {@link CCTParamsInvalidError} If a pool parameter is invalid. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedDeployTokenPool({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * payer, + * authority, + * allowlist: [allowedSender], + * createPoolSignerATA: true, + * }) + * ``` + */ + generateUnsignedDeployTokenPool( + opts: GenerateDeployTokenPoolParams, + ): Promise { + return this.#deployTokenPool.generate(this.chain, opts) + } + + /** + * Initializes a Solana token pool. + * + * @remarks + * This only sends the pool `initialize` instruction for the canonical `burn-mint` and + * `lock-release` programs selected by `poolType`; custom pool deployment is unsupported. The signer + * must be allowed to initialize the pool. + * + * **Important:** The pool requires a `pool_token_account` (the pool signer PDA's associated token + * account) to lock/release or mint on transfers. Set `createPoolSignerATA: true` to create it + * idempotently in this transaction. If omitted (defaults to `false`), create it separately with + * the returned `poolSignerAddress` via `createTokenAccount` before `setPool`, or transfers fail + * with `AccountNotInitialized (3012)`. + * + * When to use `createPoolSignerATA: true` vs. the separate + * `generateUnsignedCreateTokenAccount` op: + * - Use this option when deploying a pool that will immediately receive transfers (simplest, one tx) + * - Use the separate op for vault-owned pools or when decoupling pool initialization from ATA setup + * + * This option is Solana-only (no EVM equivalent). It is analogous to `createRecipientATA` on + * {@link mintTokens}, the same idiomatic pattern for atomicity. + * + * @see {@link createTokenAccount} + * @see {@link setPool} + * @see {@link mintTokens} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If a pool parameter is invalid. + * @throws {@link CCTTxFailedError} If transaction simulation or submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.deployTokenPool({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * createPoolSignerATA: true, + * wallet, + * }) + * ``` + */ + deployTokenPool(opts: ExecuteDeployTokenPoolParams): Promise { + return this.#deployTokenPool.execute(this.chain, opts) + } + + /** + * Builds ordered unsigned transactions that remove remote-chain configs and add new configs with + * their remote pools and rate limits. This accepts the same `remoteChainSelectorsToRemove` and + * `chainsToAdd` parameters as EVM `applyChainUpdates`. + * + * @remarks Removals run before additions. Each added chain is initialized, configured, and + * rate-limited as one transaction group; returns one or more packed transactions. To replace a + * chain, include its selector in both `remoteChainSelectorsToRemove` and `chainsToAdd`. + * Solana requires `remoteTokenDecimals`. `authority` must be the pool owner and defaults to `payer`. + * + * @see {@link applyChainUpdates} + * + * @throws {@link CCTParamsInvalidError} If a pool parameter or chain update is invalid. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsignedTxs = await cct.generateUnsignedApplyChainUpdates({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * remoteChainSelectorsToRemove: [oldSelector], + * chainsToAdd: [{ + * remoteChainSelector: newSelector, + * remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + * remotePoolAddresses: ['0x1234567890abcdef1234567890abcdef12345678'], + * remoteTokenDecimals: 18, + * inboundRateLimiterConfig: { enabled: false }, + * outboundRateLimiterConfig: { enabled: true, capacity: 1_000_000n, rate: 1_000n }, + * }], + * payer, + * authority, + * }) + * + * for (const unsignedTx of unsignedTxs) { + * // Sign and submit each transaction in order. + * } + * ``` + */ + generateUnsignedApplyChainUpdates( + opts: GenerateApplyChainUpdatesParams, + ): Promise { + return this.#applyChainUpdates.generateBatch(this.chain, opts) + } + + /** + * Applies EVM-equivalent remote-chain configuration changes with the pool owner wallet. + * + * @remarks Removals run before additions. Each added chain is initialized, configured, and + * rate-limited as one transaction group. Groups are submitted sequentially and are not atomic; + * if a later transaction fails, earlier groups may already be committed. The result contains every + * transaction hash. To replace a chain, include its selector in both `remoteChainSelectorsToRemove` + * and `chainsToAdd`. `wallet` must be the + * pool owner and is the fee payer and default authority. + * + * @see {@link generateUnsignedApplyChainUpdates} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If a pool parameter or chain update is invalid, or the + * authority differs from the executing wallet. + * @throws {@link CCTTxFailedError} If a chain config already exists or is missing, the wallet is + * not the pool owner, or simulation/submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.applyChainUpdates({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * remoteChainSelectorsToRemove: [], + * chainsToAdd: [{ + * remoteChainSelector: selector, + * remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + * remotePoolAddresses: ['0x1234567890abcdef1234567890abcdef12345678'], + * remoteTokenDecimals: 18, + * inboundRateLimiterConfig: { enabled: false }, + * outboundRateLimiterConfig: { enabled: false }, + * }], + * wallet, + * }) + * ``` + */ + applyChainUpdates(opts: ExecuteApplyChainUpdatesParams): Promise { + return this.#applyChainUpdates.executeBatch(this.chain, opts) + } + + /** + * Builds an unsigned instruction that appends remote pool addresses to an initialized Solana + * token pool remote-chain config. Pass canonical `poolType` or a compatible + * `poolProgramAddress`; `authority` defaults to `payer`. + * + * @remarks `remotePoolAddresses` must be non-empty and contain no duplicates. Existing addresses + * are retained. On-chain execution rejects addresses already present. To clear all pools, use + * `generateUnsignedEditChainRemoteConfig` with `remotePoolAddresses: []`. The remote-chain config + * must already exist. + * + * @see {@link appendRemotePoolAddresses} + * @see {@link generateUnsignedEditChainRemoteConfig} + * + * @throws {@link CCTParamsInvalidError} If a pool parameter, selector, or remote pool address is invalid. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedAppendRemotePoolAddresses({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * remoteChainSelector: 5009297550715157269n, + * remotePoolAddresses: ['0x1234567890abcdef1234567890abcdef12345678'], + * payer, + * authority, + * }) + * ``` + */ + generateUnsignedAppendRemotePoolAddresses( + opts: GenerateAppendRemotePoolAddressesParams, + ): Promise { + return this.#appendRemotePoolAddresses.generate(this.chain, opts) + } + + /** + * Appends remote pool addresses to an initialized Solana token pool remote-chain config with the + * pool owner wallet. + * + * @remarks `remotePoolAddresses` must be non-empty and contain no duplicates. Existing addresses + * are retained. The remote-chain config must already exist; addresses already on-chain cause the + * transaction to fail. To clear all pools, use `editChainRemoteConfig` with + * `remotePoolAddresses: []`. + * + * @see {@link generateUnsignedAppendRemotePoolAddresses} + * @see {@link editChainRemoteConfig} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If a pool parameter or remote pool address is invalid, or + * the authority differs from the executing wallet. + * @throws {@link CCTTxFailedError} If the chain config does not exist, the wallet is not the pool + * owner, an address already exists, or simulation/submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.appendRemotePoolAddresses({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * remoteChainSelector: 5009297550715157269n, + * remotePoolAddresses: ['0x1234567890abcdef1234567890abcdef12345678'], + * wallet, + * }) + * ``` + */ + appendRemotePoolAddresses( + opts: ExecuteAppendRemotePoolAddressesParams, + ): Promise { + return this.#appendRemotePoolAddresses.execute(this.chain, opts) + } + + /** + * Builds an unsigned instruction that initializes a Solana token pool remote-chain config for a + * previously unconfigured selector. Pass canonical `poolType` or a compatible + * `poolProgramAddress`; `authority` defaults to + * `payer`. + * + * @remarks This creates the chain-config PDA once and fails if it already exists. Configure + * remote pools and rate limits separately before using the lane. + * + * @see {@link initChainRemoteConfig} + * @see {@link generateUnsignedEditChainRemoteConfig} + * @see {@link generateUnsignedDeleteChainRemoteConfig} + * + * @throws {@link CCTParamsInvalidError} If a pool parameter or remote config value is invalid. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedInitChainRemoteConfig({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * remoteChainSelector: 5009297550715157269n, + * remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + * remoteTokenDecimals: 18, + * payer, + * authority, + * }) + * ``` + */ + generateUnsignedInitChainRemoteConfig( + opts: GenerateInitChainRemoteConfigParams, + ): Promise { + return this.#initChainRemoteConfig.generate(this.chain, opts) + } + + /** + * Initializes a Solana token pool remote-chain config for a previously unconfigured selector + * with the pool owner wallet. + * + * @remarks This creates the chain-config PDA once and fails if it already exists. Configure + * remote pools and rate limits separately before using the lane. + * + * @see {@link generateUnsignedInitChainRemoteConfig} + * @see {@link editChainRemoteConfig} + * @see {@link deleteChainRemoteConfig} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If a pool parameter is invalid or the authority differs + * from the executing wallet. + * @throws {@link CCTTxFailedError} If simulation or the pool rejects the transaction. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.initChainRemoteConfig({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * remoteChainSelector: 5009297550715157269n, + * remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + * remoteTokenDecimals: 18, + * wallet, + * }) + * ``` + */ + initChainRemoteConfig( + opts: ExecuteInitChainRemoteConfigParams, + ): Promise { + return this.#initChainRemoteConfig.execute(this.chain, opts) + } + + /** + * Builds an unsigned instruction that closes a Solana token pool remote-chain config. Pass + * canonical `poolType` or a compatible `poolProgramAddress`; `authority` defaults to `payer`. + * + * @remarks + * Destructive: this closes the remote-chain config account and returns its rent to `authority`. + * CCIP transfers for `remoteChainSelector` fail until the config is recreated with + * `generateUnsignedInitChainRemoteConfig`. On-chain execution requires `authority` to be the + * token pool owner and the chain config to exist. + * + * @see {@link deleteChainRemoteConfig} + * @see {@link generateUnsignedInitChainRemoteConfig} + * @see {@link generateUnsignedEditChainRemoteConfig} + * + * @throws {@link CCTParamsInvalidError} If a pool parameter or remote chain selector is invalid. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedDeleteChainRemoteConfig({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * remoteChainSelector: 5009297550715157269n, + * payer, + * authority, + * }) + * ``` + */ + generateUnsignedDeleteChainRemoteConfig( + opts: GenerateDeleteChainRemoteConfigParams, + ): Promise { + return this.#deleteChainRemoteConfig.generate(this.chain, opts) + } + + /** + * Closes an initialized Solana token pool remote-chain config with the pool owner wallet. + * + * @remarks + * Destructive: this closes the remote-chain config account and returns its rent to the wallet. + * CCIP transfers for `remoteChainSelector` fail until the config is recreated with + * `initChainRemoteConfig`. + * + * @see {@link generateUnsignedDeleteChainRemoteConfig} + * @see {@link initChainRemoteConfig} + * @see {@link editChainRemoteConfig} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If a pool parameter is invalid or the authority differs + * from the executing wallet. + * @throws {@link CCTTxFailedError} If the chain config does not exist, the wallet is not the pool + * owner, or simulation/submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.deleteChainRemoteConfig({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * remoteChainSelector: 5009297550715157269n, + * wallet, + * }) + * ``` + */ + deleteChainRemoteConfig( + opts: ExecuteDeleteChainRemoteConfigParams, + ): Promise { + return this.#deleteChainRemoteConfig.execute(this.chain, opts) + } + + /** + * Builds an unsigned instruction that assigns the rate-limit admin for an initialized Solana + * token pool. Pass canonical `poolType` or a compatible `poolProgramAddress`; `authority` + * defaults to `payer`. + * + * @remarks On-chain execution requires `authority` to be the pool owner. This assignment takes + * effect immediately; unlike ownership transfer, it has no acceptance step. The new rate-limit + * admin may configure chain rate limits but cannot change this role. + * + * @see {@link setRateLimitAdmin} + * @see {@link generateUnsignedSetChainRateLimit} + * + * @throws {@link CCTParamsInvalidError} If a pool parameter or public key is invalid. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedSetRateLimitAdmin({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * newRateLimitAdmin, + * payer, + * authority, + * }) + * ``` + */ + generateUnsignedSetRateLimitAdmin( + opts: GenerateSetRateLimitAdminParams, + ): Promise { + return this.#setRateLimitAdmin.generate(this.chain, opts) + } + + /** + * Assigns the rate-limit admin for an initialized Solana token pool with the pool owner wallet. + * + * @remarks This assignment takes effect immediately; unlike ownership transfer, it has no + * acceptance step. The new rate-limit admin may configure chain rate limits but cannot change + * this role. + * + * @see {@link generateUnsignedSetRateLimitAdmin} + * @see {@link setChainRateLimit} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If a pool parameter is invalid or the authority differs + * from the executing wallet. + * @throws {@link CCTTxFailedError} If the pool does not exist, the wallet is not the pool owner, + * or simulation/submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.setRateLimitAdmin({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * newRateLimitAdmin, + * wallet, + * }) + * ``` + */ + setRateLimitAdmin(opts: ExecuteSetRateLimitAdminParams): Promise { + return this.#setRateLimitAdmin.execute(this.chain, opts) + } + + /** + * Builds an unsigned instruction to deposit a rebalancer's tokens into a lock-release pool. + * Pass `poolType: 'lock-release'` or a compatible `poolProgramAddress`; a custom program must + * have the canonical lock-release `provideLiquidity` instruction and account layout. `authority` + * defaults to `payer`. `amount` is a positive u64 in base units. + * + * @remarks The pool config must have `canAcceptLiquidity: true` and a `rebalancer` equal to the + * transaction authority. The authority's ATA for `tokenAddress` must exist, hold at least `amount`, + * and delegate at least `amount` to the pool signer PDA. Set `includeApproval: true` to bundle + * that approval before the liquidity instruction in this transaction. + * + * @see {@link provideLiquidity} + * @see {@link generateUnsignedApproveToken} + * @see {@link setRebalancer} + * @see {@link setCanAcceptLiquidity} + * + * @throws {@link CCTParamsInvalidError} If a pool parameter, address, or amount is invalid. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * @throws {@link CCIPTokenPoolStateNotFoundError} If the token pool state is missing. + * @throws {@link CCIPTokenAccountNotFoundError} If the rebalancer or pool vault ATA is missing; create it first. + * + * @example Generate bundled approval and liquidity instructions + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const liquidity = await cct.generateUnsignedProvideLiquidity({ + * payer: rebalancer, + * tokenAddress: mint, + * poolType: 'lock-release', + * amount: 1_000_000n, + * includeApproval: true, + * }) + * ``` + */ + generateUnsignedProvideLiquidity( + opts: GenerateProvideLiquidityParams, + ): Promise { + return this.#provideLiquidity.generate(this.chain, opts) + } + + /** + * Deposits tokens from the executing rebalancer wallet into a lock-release pool. + * Pass `poolType: 'lock-release'` or a compatible `poolProgramAddress`; a custom program must + * have the canonical lock-release `provideLiquidity` instruction and account layout. The wallet's + * associated token account must exist and hold the positive u64 `amount` in base units. + * + * @remarks The pool config must have `canAcceptLiquidity: true` and a `rebalancer` equal to the + * transaction authority. Before this operation, the rebalancer ATA must delegate at least `amount` + * to the pool signer PDA, unless `includeApproval: true` bundles that approval in this transaction. + * + * @see {@link generateUnsignedProvideLiquidity} + * @see {@link approveToken} + * @see {@link setRebalancer} + * @see {@link setCanAcceptLiquidity} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If a pool parameter, address, or amount is invalid, or + * the authority differs from the executing wallet. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * @throws {@link CCIPTokenPoolStateNotFoundError} If the token pool state is missing. + * @throws {@link CCIPTokenAccountNotFoundError} If the rebalancer or pool vault ATA is missing; create it first. + * @throws {@link CCTTxFailedError} If the source ATA does not delegate enough tokens to the pool + * signer and `includeApproval` is false, the pool rejects the rebalancer, liquidity is disabled, + * the token account lacks funds, or simulation/submission fails. + * + * @example Approve and provide liquidity in one transaction + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.provideLiquidity({ + * wallet, + * tokenAddress: mint, + * poolType: 'lock-release', + * amount: 1_000_000n, + * includeApproval: true, + * }) + * ``` + */ + provideLiquidity(opts: ExecuteProvideLiquidityParams): Promise { + return this.#provideLiquidity.execute(this.chain, opts) + } + + /** + * Builds an unsigned instruction to withdraw tokens from a lock-release pool to a rebalancer's + * associated token account. Pass `poolType: 'lock-release'` or a compatible `poolProgramAddress`; + * a custom program must have the canonical lock-release `withdrawLiquidity` instruction and account + * layout. `authority` defaults to `payer`. `amount` is a positive u64 in base units. + * + * @remarks The pool config must have `canAcceptLiquidity: true` and a `rebalancer` equal to the + * transaction authority. The rebalancer's associated token account must already exist. + * + * @see {@link withdrawLiquidity} + * @see {@link generateUnsignedSetRebalancer} + * @see {@link generateUnsignedSetCanAcceptLiquidity} + * + * @throws {@link CCTParamsInvalidError} If a pool parameter, address, or amount is invalid. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * + * @example Generate a liquidity withdrawal instruction + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const withdrawal = await cct.generateUnsignedWithdrawLiquidity({ + * payer: rebalancer, + * tokenAddress: mint, + * poolType: 'lock-release', + * amount: 1_000_000n, + * }) + * ``` + */ + generateUnsignedWithdrawLiquidity( + opts: GenerateWithdrawLiquidityParams, + ): Promise { + return this.#withdrawLiquidity.generate(this.chain, opts) + } + + /** + * Withdraws tokens from a lock-release pool into the executing rebalancer wallet's associated + * token account. Pass `poolType: 'lock-release'` or a compatible `poolProgramAddress`; a custom + * program must have the canonical lock-release `withdrawLiquidity` instruction and account layout. + * The wallet's associated token account must exist. `amount` is a positive u64 in base units. + * + * @remarks The pool config must have `canAcceptLiquidity: true` and a `rebalancer` equal to the + * transaction authority. + * + * @see {@link generateUnsignedWithdrawLiquidity} + * @see {@link setRebalancer} + * @see {@link setCanAcceptLiquidity} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If a pool parameter, address, or amount is invalid, or + * the authority differs from the executing wallet. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * @throws {@link CCTTxFailedError} If the pool rejects the rebalancer, liquidity is disabled, + * lacks liquidity, the token account does not exist, or simulation/submission fails. + * + * @example Withdraw liquidity + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.withdrawLiquidity({ + * wallet, + * tokenAddress: mint, + * poolType: 'lock-release', + * amount: 1_000_000n, + * }) + * ``` + */ + withdrawLiquidity(opts: ExecuteWithdrawLiquidityParams): Promise { + return this.#withdrawLiquidity.execute(this.chain, opts) + } + + /** + * Builds an unsigned instruction that sets whether an initialized Solana lock-release token pool + * accepts `provideLiquidity` deposits and `withdrawLiquidity` transfers. Pass canonical + * `poolType: 'lock-release'` or a compatible `poolProgramAddress`; `authority` defaults to `payer`. + * + * @remarks + * ⚠️ **Consequence:** Setting `allow` to `true` lets the rebalancer both `provideLiquidity` and + * `withdrawLiquidity`. Setting `allow` to `false` **disables both** — liquidity already in the pool cannot be + * withdrawn until `allow` is re-enabled. Verify the current liquidity balance before flipping to `false`. + * + * @see {@link setCanAcceptLiquidity} + * @see {@link generateUnsignedSetRebalancer} + * + * @throws {@link CCTParamsInvalidError} If `allow`, a pool parameter, or public key is invalid. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedSetCanAcceptLiquidity({ + * tokenAddress: mint, + * poolType: 'lock-release', + * allow: true, + * payer, + * authority, + * }) + * ``` + */ + generateUnsignedSetCanAcceptLiquidity( + opts: GenerateSetCanAcceptLiquidityParams, + ): Promise { + return this.#setCanAcceptLiquidity.generate(this.chain, opts) + } + + /** + * Sets whether an initialized Solana lock-release token pool accepts `provideLiquidity` deposits + * and `withdrawLiquidity` transfers using the pool owner wallet. + * + * @remarks + * ⚠️ **Consequence:** Setting `allow` to `true` lets the rebalancer both `provideLiquidity` and + * `withdrawLiquidity`. Setting `allow` to `false` **disables both** — liquidity already in the pool cannot be + * withdrawn until `allow` is re-enabled. Verify the current liquidity balance before flipping to `false`. + * + * @see {@link generateUnsignedSetCanAcceptLiquidity} + * @see {@link setRebalancer} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If `allow` or a pool parameter is invalid, or the authority differs + * from the executing wallet. + * @throws {@link CCTTxFailedError} If the wallet is not the pool owner or simulation/submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.setCanAcceptLiquidity({ + * tokenAddress: mint, + * poolType: 'lock-release', + * allow: true, + * wallet, + * }) + * ``` + */ + setCanAcceptLiquidity( + opts: ExecuteSetCanAcceptLiquidityParams, + ): Promise { + return this.#setCanAcceptLiquidity.execute(this.chain, opts) + } + + /** + * Builds an unsigned instruction that sets the address authorized to provide or withdraw + * liquidity for an initialized Solana lock-release token pool. Pass canonical + * `poolType: 'lock-release'` or a compatible `poolProgramAddress`; `authority` defaults to + * `payer`. The default/zero public key (`11111111111111111111111111111111`) disables + * rebalancing. + * + * @remarks + * ⚠️ **Consequence:** Rebalancer is the address allowed to provide or withdraw liquidity. + * Setting the zero address (`11111111111111111111111111111111`) removes the rebalancer; until a new one + * is set, **no account can provide or withdraw liquidity**, even liquidity already in the pool. + * This does not affect whether the pool accepts liquidity — see {@link setCanAcceptLiquidity}. + * + * @see {@link setRebalancer} + * @see {@link setCanAcceptLiquidity} + * @see {@link generateUnsignedSetCanAcceptLiquidity} + * + * @throws {@link CCTParamsInvalidError} If a pool parameter or public key is invalid. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedSetRebalancer({ + * tokenAddress: mint, + * poolType: 'lock-release', + * rebalancer, + * payer, + * authority, + * }) + * ``` + */ + generateUnsignedSetRebalancer( + opts: GenerateSetRebalancerParams, + ): Promise { + return this.#setRebalancer.generate(this.chain, opts) + } + + /** + * Sets the address authorized to provide or withdraw liquidity for an initialized Solana + * lock-release token pool using the pool owner wallet. Pass canonical `poolType: 'lock-release'` + * or a compatible `poolProgramAddress`; set `rebalancer` to the default/zero public key + * (`11111111111111111111111111111111`) to disable rebalancing. + * + * @remarks + * ⚠️ **Consequence:** Rebalancer is the address allowed to provide or withdraw liquidity. + * Setting the zero address (`11111111111111111111111111111111`) removes the rebalancer; until a new one + * is set, **no account can provide or withdraw liquidity**, even liquidity already in the pool. + * This does not affect whether the pool accepts liquidity — see {@link setCanAcceptLiquidity}. + * + * @see {@link generateUnsignedSetRebalancer} + * @see {@link setCanAcceptLiquidity} + * @see {@link generateUnsignedSetCanAcceptLiquidity} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If a pool parameter is invalid or the authority differs + * from the executing wallet. + * @throws {@link CCTTxFailedError} If the wallet is not the pool owner or simulation/submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.setRebalancer({ + * tokenAddress: mint, + * poolType: 'lock-release', + * rebalancer, + * wallet, + * }) + * ``` + * + * @example Disable rebalancing + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.setRebalancer({ + * tokenAddress: mint, + * poolType: 'lock-release', + * rebalancer: PublicKey.default.toBase58(), // disable + * wallet, + * }) + * ``` + */ + setRebalancer(opts: ExecuteSetRebalancerParams): Promise { + return this.#setRebalancer.execute(this.chain, opts) + } + + /** + * Builds an unsigned instruction that proposes a new owner for an initialized Solana token pool. + * Pass canonical `poolType` or a compatible `poolProgramAddress`; `authority` defaults to `payer`. + * The operation reads pool state and rejects the current owner or default public key. The proposed + * owner must accept ownership separately before the transfer takes effect. + * + * @see {@link transferOwnership} + * @see {@link generateUnsignedAcceptOwnership} + * + * @throws {@link CCTParamsInvalidError} If a pool parameter or public key is invalid. + * @throws {@link CCIPTokenPoolStateNotFoundError} If the token pool account does not exist. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedTransferOwnership({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * newOwner, + * payer, + * authority, + * }) + * ``` + */ + generateUnsignedTransferOwnership( + opts: GenerateTransferOwnershipParams, + ): Promise { + return this.#transferOwnership.generate(this.chain, opts) + } + + /** + * Proposes a new owner for an initialized Solana token pool using the current owner wallet. + * It rejects the current owner or default public key. The proposed owner must accept ownership + * separately before the transfer takes effect. + * + * @see {@link generateUnsignedTransferOwnership} + * @see {@link acceptOwnership} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If a pool parameter is invalid or the authority differs + * from the executing wallet. + * @throws {@link CCIPTokenPoolStateNotFoundError} If the token pool account does not exist. + * @throws {@link CCTTxFailedError} If the wallet is not the pool owner or simulation/submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.transferOwnership({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * newOwner, + * wallet, + * }) + * ``` + */ + transferOwnership(opts: ExecuteTransferOwnershipParams): Promise { + return this.#transferOwnership.execute(this.chain, opts) + } + + /** + * Builds an unsigned instruction that accepts pending ownership of an initialized Solana token + * pool. Pass canonical `poolType` or a compatible `poolProgramAddress`; `authority` defaults to + * `payer`. The operation reads pool state and requires it to be the proposed owner. + * + * @see {@link acceptOwnership} + * @see {@link generateUnsignedTransferOwnership} + * + * @throws {@link CCTParamsInvalidError} If a pool parameter or public key is invalid. + * @throws {@link CCIPTokenPoolStateNotFoundError} If the token pool account does not exist. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedAcceptOwnership({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * payer, + * authority, + * }) + * ``` + */ + generateUnsignedAcceptOwnership( + opts: GenerateAcceptOwnershipParams, + ): Promise { + return this.#acceptOwnership.generate(this.chain, opts) + } + + /** + * Accepts pending ownership of an initialized Solana token pool using the proposed owner wallet. + * It verifies the wallet is the proposed owner before submitting. + * + * @see {@link generateUnsignedAcceptOwnership} + * @see {@link transferOwnership} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If a pool parameter is invalid or the authority differs + * from the executing wallet. + * @throws {@link CCIPTokenPoolStateNotFoundError} If the token pool account does not exist. + * @throws {@link CCTTxFailedError} If the wallet is not the proposed owner or + * simulation/submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.acceptOwnership({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * wallet, + * }) + * ``` + */ + acceptOwnership(opts: ExecuteAcceptOwnershipParams): Promise { + return this.#acceptOwnership.execute(this.chain, opts) + } + + /** + * Builds an unsigned instruction that sets inbound and outbound rate limits for an initialized + * Solana token pool remote-chain config. Pass canonical `poolType` or a compatible + * `poolProgramAddress`; `authority` defaults to `payer`. + * + * @remarks On-chain execution requires `authority` to be the pool owner or rate-limit admin. + * The remote-chain config must already exist. Enabled limits require `rate <= capacity`; + * disabled limits default omitted values to zero and reject nonzero values. + * + * @see {@link setChainRateLimit} + * @see {@link generateUnsignedInitChainRemoteConfig} + * + * @throws {@link CCTParamsInvalidError} If a pool parameter, rate limit, or selector is invalid. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedSetChainRateLimit({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * remoteChainSelector: 5009297550715157269n, + * inbound: { enabled: true, capacity: 1_000_000n, rate: 1_000n }, + * outbound: { enabled: false }, // Disabled limits default capacity and rate to zero. + * payer, + * authority, + * }) + * ``` + */ + generateUnsignedSetChainRateLimit( + opts: GenerateSetChainRateLimitParams, + ): Promise { + return this.#setChainRateLimit.generate(this.chain, opts) + } + + /** + * Sets inbound and outbound rate limits for an initialized Solana token pool remote-chain config + * with the pool owner or rate-limit admin wallet. + * + * @remarks The remote-chain config must already exist. Enabled limits require `rate <= capacity`; + * disabled limits default omitted values to zero and reject nonzero values. + * + * @see {@link generateUnsignedSetChainRateLimit} + * @see {@link initChainRemoteConfig} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If a pool parameter or rate limit is invalid, or the + * authority differs from the executing wallet. + * @throws {@link CCTTxFailedError} If the chain config does not exist, the wallet is neither the + * pool owner nor rate-limit admin, or simulation/submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.setChainRateLimit({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * remoteChainSelector: 5009297550715157269n, + * inbound: { enabled: true, capacity: 1_000_000n, rate: 1_000n }, + * outbound: { enabled: false }, // Disabled limits default capacity and rate to zero. + * wallet, + * }) + * ``` + */ + setChainRateLimit(opts: ExecuteSetChainRateLimitParams): Promise { + return this.#setChainRateLimit.execute(this.chain, opts) + } + + /** + * Builds an unsigned instruction that replaces an initialized Solana token pool remote-chain + * config. Initialize the config first with `generateUnsignedInitChainRemoteConfig`. Each call + * replaces the remote token address, pool addresses, and decimals. Pass canonical `poolType` or + * a compatible `poolProgramAddress`; `authority` defaults to `payer`. + * + * @see {@link editChainRemoteConfig} + * @see {@link generateUnsignedInitChainRemoteConfig} + * @see {@link generateUnsignedDeleteChainRemoteConfig} + * + * @throws {@link CCTParamsInvalidError} If a pool parameter or remote config value is invalid. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedEditChainRemoteConfig({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * remoteChainSelector: 5009297550715157269n, + * remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + * remotePoolAddresses: ['0x1234567890abcdef1234567890abcdef12345678'], + * remoteTokenDecimals: 18, + * payer, + * authority, + * }) + * ``` + */ + generateUnsignedEditChainRemoteConfig( + opts: GenerateEditChainRemoteConfigParams, + ): Promise { + return this.#editChainRemoteConfig.generate(this.chain, opts) + } + + /** + * Replaces an initialized Solana token pool remote-chain config with the pool owner wallet. + * Initialize the config first with `initChainRemoteConfig`. Each call replaces the remote token + * address, pool addresses, and decimals. + * + * @see {@link generateUnsignedEditChainRemoteConfig} + * @see {@link initChainRemoteConfig} + * @see {@link deleteChainRemoteConfig} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If a pool parameter is invalid or the authority differs + * from the executing wallet. + * @throws {@link CCTTxFailedError} If simulation or the pool rejects the transaction. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.editChainRemoteConfig({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * remoteChainSelector: 5009297550715157269n, + * remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + * remotePoolAddresses: ['0x1234567890abcdef1234567890abcdef12345678'], + * remoteTokenDecimals: 18, + * wallet, + * }) + * ``` + */ + editChainRemoteConfig( + opts: ExecuteEditChainRemoteConfigParams, + ): Promise { + return this.#editChainRemoteConfig.execute(this.chain, opts) + } + + /** + * Builds unsigned Solana lookup table extend instructions. + * + * Pass `tokenAddress` with a canonical `poolType` or custom `poolProgramAddress` to append the + * standard CCIP pool addresses; pass `additionalAddresses` to append manual addresses. `authority` + * defaults to `payer`. + * + * @throws {@link CCTParamsInvalidError} If an address or lookup table parameter is invalid. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedAppendToLookupTable({ + * lookupTableAddress, + * payer: squadsVault, + * authority: squadsVault, + * tokenAddress: mint, + * poolProgramAddress: poolProgram, + * additionalAddresses: [extraAccount], + * }) + * ``` + */ + generateUnsignedAppendToLookupTable( + opts: GenerateAppendToLookupTableParams, + ): Promise { + return this.#appendToLookupTable.generate(this.chain, opts) + } + + /** + * Extends a Solana lookup table. + * + * Pass `tokenAddress` with a canonical `poolType` or custom `poolProgramAddress` to append the + * standard CCIP pool addresses. + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If an address or lookup table parameter is invalid. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * @throws {@link CCTTxFailedError} If transaction simulation or submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.appendToLookupTable({ + * lookupTableAddress, + * wallet, + * tokenAddress: mint, + * poolProgramAddress: poolProgram, + * additionalAddresses: [extraAccount], + * }) + * ``` + */ + appendToLookupTable( + opts: ExecuteAppendToLookupTableParams, + ): Promise { + return this.#appendToLookupTable.execute(this.chain, opts) + } + + /** + * Builds an unsigned Solana instruction that accepts a pending token administrator role. + * + * The supplied authority must be the pending token administrator. + * + * @remarks + * Call this after {@link generateUnsignedRegisterAdmin} or {@link generateUnsignedTransferAdmin} + * and before {@link generateUnsignedSetPool}. `authority` defaults to `payer`; Squads/vault + * flows should use this method with their fee payer and signing authority explicitly. + * + * @see {@link generateUnsignedRegisterAdmin} + * @see {@link generateUnsignedTransferAdmin} + * @see {@link generateUnsignedSetPool} + * + * @throws {@link CCTParamsInvalidError} If an address is invalid or the authority is not the + * pending token administrator. + * @throws {@link CCIPContractNotRouterError} If `address` does not resolve to a Router. + * @throws {@link CCIPTokenNotConfiguredError} If the token is not registered. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedAcceptAdmin({ + * tokenAddress: mint, + * address: router, + * payer: pendingAdmin, + * }) + * ``` + */ + generateUnsignedAcceptAdmin(opts: GenerateAcceptAdminParams): Promise { + return this.#acceptAdmin.generate(this.chain, opts) + } + + /** + * Accepts a pending token administrator role using the pending administrator wallet. + * + * @remarks + * Call this after {@link registerAdmin} or {@link transferAdmin} and before {@link setPool}. + * `authority` defaults to `wallet`; Squads/vault flows should use + * {@link generateUnsignedAcceptAdmin} instead. + * + * @see {@link registerAdmin} + * @see {@link transferAdmin} + * @see {@link setPool} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If an address is invalid or `authority` does not match + * the executing wallet/pending token administrator. + * @throws {@link CCIPContractNotRouterError} If `address` does not resolve to a Router. + * @throws {@link CCIPTokenNotConfiguredError} If the token is not registered. + * @throws {@link CCTTxFailedError} If simulation or the Router rejects the transaction. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.acceptAdmin({ tokenAddress: mint, address: router, wallet: pendingAdminWallet }) + * ``` + */ + acceptAdmin(opts: ExecuteAcceptAdminParams): Promise { + return this.#acceptAdmin.execute(this.chain, opts) + } + + /** + * Builds an unsigned instruction that replaces an initial pending registry administrator. + * + * @remarks + * Only the mint authority may authorize this recovery path, and only while the registry has no + * accepted administrator. It replaces the initial pending administrator; the replacement must + * still call {@link generateUnsignedAcceptAdmin}. `authority` defaults to `payer`; use this + * unsigned method for Squads/vault signatures. + * + * @see {@link ownerOverridePendingAdministrator} For wallet-based execution. + * @see {@link generateUnsignedAcceptAdmin} The replacement administrator must accept separately. + * + * @throws {@link CCTParamsInvalidError} If an address is invalid or the registry already has an + * accepted administrator. + * @throws {@link CCIPContractNotRouterError} If `address` does not resolve to a Router. + * @throws {@link CCIPTokenNotConfiguredError} If the token is not registered. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedOwnerOverridePendingAdministrator({ + * tokenAddress: mint, + * address: router, + * newAdmin: replacementAdmin, + * payer: mintAuthority, + * }) + * ``` + */ + generateUnsignedOwnerOverridePendingAdministrator( + opts: GenerateOwnerOverridePendingAdministratorParams, + ): Promise { + return this.#ownerOverridePendingAdministrator.generate(this.chain, opts) + } + + /** + * Replaces an initial pending registry administrator using the mint authority wallet. + * + * @remarks + * This recovery path only works while the registry has no accepted administrator. It replaces the + * initial pending administrator; it does not make the replacement an administrator. The replacement + * must call {@link acceptAdmin} separately. `authority` defaults to `wallet`; use + * {@link generateUnsignedOwnerOverridePendingAdministrator} for Squads/vault flows. + * + * @see {@link generateUnsignedOwnerOverridePendingAdministrator} For externally signed transactions. + * @see {@link acceptAdmin} The replacement administrator must accept the role separately. + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If an address is invalid, the registry already has an accepted + * administrator, or `authority` differs from the wallet. + * @throws {@link CCIPContractNotRouterError} If `address` does not resolve to a Router. + * @throws {@link CCIPTokenNotConfiguredError} If the token is not registered. + * @throws {@link CCTTxFailedError} If the Router rejects a non-mint authority or the registry changes. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.ownerOverridePendingAdministrator({ + * tokenAddress: mint, + * address: router, + * newAdmin: replacementAdmin, + * wallet: mintAuthorityWallet, + * }) + * ``` + */ + ownerOverridePendingAdministrator( + opts: ExecuteOwnerOverridePendingAdministratorParams, + ): Promise { + return this.#ownerOverridePendingAdministrator.execute(this.chain, opts) + } + + /** + * Builds an unsigned Solana token registration instruction. + * + * This proposes the registry administrator. The proposed admin must accept the role using + * {@link generateUnsignedAcceptAdmin} before calling {@link generateUnsignedSetPool}. The + * administrator defaults to the mint authority and the method to `owner`; + * choose `ccip-admin` when the Router CCIP admin signs. Provide `administrator` + * to nominate a different admin or register a mint with no mint authority. + * + * @see {@link generateUnsignedAcceptAdmin} + * @see {@link generateUnsignedSetPool} + * + * @throws {@link CCTParamsInvalidError} If an address or `registrationMethod` is invalid, the + * authority does not match the selected registration method, `administrator` is required, or a + * registry entry already exists for the token. + * @throws {@link CCIPContractNotRouterError} If `address` does not resolve to a Router. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedRegisterAdmin({ + * tokenAddress: mint, + * address: router, + * payer: mintAuthority, + * }) + * ``` + */ + generateUnsignedRegisterAdmin( + opts: GenerateRegisterAdminParams, + ): Promise { + return this.#registerAdmin.generate(this.chain, opts) + } + + /** + * Proposes a token registry administrator using the executing wallet as registration authority + * and fee payer. The proposed admin must {@link acceptAdmin} before calling {@link setPool}. + * + * @see {@link acceptAdmin} + * @see {@link setPool} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If an address or `registrationMethod` is invalid, the + * authority does not match the selected registration method or executing wallet, + * `administrator` is required, or a registry entry already exists for the token. + * @throws {@link CCIPContractNotRouterError} If `address` does not resolve to a Router. + * @throws {@link CCIPTokenMintNotFoundError} If the mint does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * @throws {@link CCTTxFailedError} If simulation or the Router rejects the transaction. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.registerAdmin({ + * tokenAddress: mint, + * address: router, + * wallet, + * }) + * ``` + */ + registerAdmin(opts: ExecuteRegisterAdminParams): Promise { + return this.#registerAdmin.execute(this.chain, opts) + } + + /** + * Builds an unsigned instruction to remove addresses from a token pool allowlist. The pool must + * be initialized first. Pass canonical `poolType` or a compatible `poolProgramAddress`; + * `authority` defaults to `payer`. Every removed address must already be allowlisted or the + * transaction reverts. + * + * @remarks Removal does not change enforcement; removing the last allowed sender while the + * allowlist is enabled blocks all senders — use `configureAllowlist` to toggle. + * + * @see {@link generateUnsignedConfigureAllowlist} + * @see {@link removeFromAllowlist} + * + * @throws {@link CCTParamsInvalidError} If a pool parameter is invalid. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedRemoveFromAllowlist({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * remove: [sender], + * payer, + * authority, + * }) + * ``` + */ + generateUnsignedRemoveFromAllowlist( + opts: GenerateRemoveFromAllowlistParams, + ): Promise { + return this.#removeFromAllowlist.generate(this.chain, opts) + } + + /** + * Removes addresses from an initialized Solana token pool allowlist using the pool owner wallet. + * Every removed address must already be allowlisted or the transaction reverts. + * + * @remarks Removal does not change enforcement; removing the last allowed sender while the + * allowlist is enabled blocks all senders — use `configureAllowlist` to toggle. + * + * @see {@link configureAllowlist} + * @see {@link generateUnsignedRemoveFromAllowlist} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If a pool parameter is invalid or the authority differs + * from the executing wallet. + * @throws {@link CCTTxFailedError} If transaction simulation or submission fails. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.removeFromAllowlist({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * remove: [sender], + * wallet, + * }) + * ``` + */ + removeFromAllowlist( + opts: ExecuteRemoveFromAllowlistParams, + ): Promise { + return this.#removeFromAllowlist.execute(this.chain, opts) + } + + /** + * Builds unsigned Solana `setPool` instructions. + * + * The token must first be registered and its proposed administrator accepted. The `payer` pays + * transaction fees; `authority` defaults to `payer`, while Squads/multisig flows should pass + * the token admin/vault authority explicitly. For a newly deployed canonical pool, create the + * pool signer's ATA before calling this operation. + * + * @see {@link generateUnsignedRegisterAdmin} + * @see {@link generateUnsignedAcceptAdmin} + * @see {@link generateUnsignedDeployTokenPool} + * @see {@link generateUnsignedCreateTokenAccount} + * + * @throws {@link CCTParamsInvalidError} If an address or `writableIndexes` is invalid. + * @throws {@link CCIPContractNotRouterError} If `address` does not resolve to a Router. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedSetPool({ + * tokenAddress: mint, + * address: router, + * poolLookupTableAddress: lookupTable, + * payer: squadsVault, + * authority: tokenAdmin, + * }) + * ``` + */ + generateUnsignedSetPool(opts: GenerateSetPoolParams): Promise { + return this.#setPool.generate(this.chain, opts) + } + + /** + * Registers a token pool. The token must first be registered and its proposed administrator + * accepted; the wallet must be the token admin authority. For a newly deployed canonical pool, + * create the pool signer's ATA before calling this operation. + * + * @see {@link registerAdmin} + * @see {@link acceptAdmin} + * @see {@link deployTokenPool} + * @see {@link createTokenAccount} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If an address or `writableIndexes` is invalid. + * @throws {@link CCIPContractNotRouterError} If `address` does not resolve to a Router. + * @throws {@link CCTTxFailedError} If simulation or the Router rejects the transaction. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.setPool({ + * tokenAddress: mint, + * address: router, + * poolLookupTableAddress: lookupTable, + * wallet, + * }) + * ``` + */ + setPool(opts: ExecuteSetPoolParams): Promise { + return this.#setPool.execute(this.chain, opts) + } + + /** + * Builds an unsigned Solana instruction that transfers a token administrator role. + * + * @remarks + * This transfers an already accepted administrator role; it does not register a token. The + * proposed administrator must call {@link generateUnsignedAcceptAdmin} before becoming the + * current administrator. + * + * @see {@link generateUnsignedAcceptAdmin} + * + * @throws {@link CCTParamsInvalidError} If an address is invalid or the authority is not the + * current token administrator. + * @throws {@link CCIPContractNotRouterError} If `address` does not resolve to a Router. + * @throws {@link CCIPTokenNotConfiguredError} If the token is not registered. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedTransferAdmin({ + * tokenAddress: mint, + * address: router, + * newAdmin, + * payer: currentAdmin, + * }) + * ``` + */ + generateUnsignedTransferAdmin( + opts: GenerateTransferAdminParams, + ): Promise { + return this.#transferAdmin.generate(this.chain, opts) + } + + /** + * Transfers a token administrator role using the executing wallet as the current administrator. + * + * @remarks + * This transfers an already accepted administrator role; it does not register a token. The + * proposed administrator must call {@link acceptAdmin} before becoming the current administrator. + * + * @see {@link acceptAdmin} + * + * @throws {@link CCIPWalletInvalidError} If `wallet` cannot sign Solana transactions. + * @throws {@link CCTParamsInvalidError} If an address is invalid or `authority` does not match + * the executing wallet/current token administrator. + * @throws {@link CCIPContractNotRouterError} If `address` does not resolve to a Router. + * @throws {@link CCIPTokenNotConfiguredError} If the token is not registered. + * @throws {@link CCTTxFailedError} If simulation or the Router rejects the transaction. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * await cct.transferAdmin({ + * tokenAddress: mint, + * address: router, + * newAdmin, + * wallet: currentAdminWallet, + * }) + * ``` + */ + transferAdmin(opts: ExecuteTransferAdminParams): Promise { + return this.#transferAdmin.execute(this.chain, opts) + } + + /** + * Reads an SPL token mint's metadata, program, supply, initialization state, and mint/freeze + * authorities. + * + * @remarks Metadata comes from {@link SolanaChain.getTokenInfo}; mint state comes directly from + * the SPL Token or Token-2022 mint account. Supply is in base units. Solana-only; no EVM CCT + * equivalent exists. + * + * @see {@link setTokenAuthority} Sets the mint or freeze authorities returned here. + * @see {@link updateMetadataAuthority} Updates the Metaplex metadata associated with this mint. + * @see {@link getTokenPoolState} Reads pool configuration rather than mint state. + * @see {@link SolanaChain.getTokenInfo} Reads the underlying token metadata. + * + * @throws {@link CCTParamsInvalidError} If `tokenAddress` is not a valid Solana public key. + * @throws {@link CCIPSplTokenInvalidError} If the token metadata is not a valid SPL token. + * @throws {@link CCIPTokenMintNotFoundError} If the mint account does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * @throws {@link CCIPTokenDataParseError} If the mint data cannot be parsed. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const info = await cct.getTokenInfo({ tokenAddress: mint }) + * console.log(`${info.symbol}: ${info.decimals} decimals`) + * ``` + */ + getTokenInfo(opts: GetTokenInfoParams): Promise { + return this.#getTokenInfo.query(this.chain, opts) + } + + /** + * Reads all, or one selected, Solana token pool remote-chain configurations. + * + * @remarks Results are keyed by remote network name. Omit `remoteChainSelector` to scan all + * configured remotes; provide it to query one. Rate-limit amounts use the local mint's smallest + * unit. + * + * @throws {@link CCTParamsInvalidError} If the token or pool program address or remote selector is invalid. + * @throws {@link CCIPTokenPoolStateNotFoundError} If the pool state account does not exist. + * @throws {@link CCIPTokenPoolChainConfigNotFoundError} If the selected remote-chain config does not exist. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const remotes = await cct.getTokenPoolRemotes({ + * tokenAddress: mint, + * poolType: 'burn-mint', + * remoteChainSelector: 5009297550715157269n, + * }) + * console.log(remotes) + * ``` + */ + getTokenPoolRemotes(opts: GetTokenPoolRemotesParams): Promise { + return this.#getTokenPoolRemotes.query(this.chain, opts) + } + + /** + * Reads a Lock/Release token pool's state account, whose config also reports its liquidity + * fields (`rebalancer`, `canAcceptLiquidity`). + * + * @remarks The EVM counterpart, `EVMTokenManager.getTokenPoolState`, returns a different shape: + * its fields are flat where these nest under `state.config`, it spells `config.mint` / + * `config.decimals` / `config.rmnRemote` as `token` / `tokenDecimals` / `rmnProxy`, and its + * `version` is the pool's protocol semver (`'2.0.0'`), not the account-layout number returned + * here. `owner`, `rateLimitAdmin` and `router` are named alike on both. + * + * @throws {@link CCTParamsInvalidError} If the token or pool program address is invalid. + * @throws {@link CCIPTokenPoolStateNotFoundError} If the pool state account does not exist. + * @throws {@link CCTDataDecodeError} If the pool state account cannot be decoded. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const state = await cct.getTokenPoolState({ + * poolType: 'lock-release', + * tokenAddress: mint, + * }) + * // config.owner must sign pool writes; config.rateLimitAdmin may set rate limits + * console.log(state.config.owner, state.config.mint, state.config.decimals) + * // lock-release only: who rebalances the pool, and whether it accepts liquidity + * console.log(state.config.rebalancer, state.config.canAcceptLiquidity) + * ``` + */ + getTokenPoolState( + opts: LockReleasePoolProgramRef & { tokenAddress: string }, + ): Promise + /** + * Reads a Burn/Mint or custom token pool's state account; its config carries no liquidity + * fields. Pass `poolProgramAddress` instead of `poolType` for a custom pool program. + */ + getTokenPoolState( + opts: (BurnMintPoolProgramRef | CustomPoolProgramRef) & { + tokenAddress: string + }, + ): Promise + /** + * Reads a pool state account whose program is not known statically; narrow the result on the + * presence of the lock-release-only config fields. + */ + getTokenPoolState(opts: GetTokenPoolStateParams): Promise + /** + * Implementation for the overloads above; callers always resolve to one of those. + * */ + getTokenPoolState(opts: GetTokenPoolStateParams): Promise { + return this.#getTokenPoolState.query(this.chain, opts) + } + + /** + * Reads a token's TokenAdminRegistry administrator, pending administrator, and pool lookup table. + * + * @throws {@link CCTParamsInvalidError} If `address` or `tokenAddress` is not a valid Solana public key. + * @throws {@link CCIPContractNotRouterError} If `address` does not resolve to a Router. + * @throws {@link CCIPTokenNotConfiguredError} If the token is not registered. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const config = await cct.getTokenAdminRegistry({ + * address: router, + * tokenAddress: mint, + * }) + * ``` + */ + getTokenAdminRegistry(opts: GetTokenAdminRegistryParams): Promise { + return this.#getTokenAdminRegistry.query(this.chain, opts) + } + + /** + * Lists all SPL token mints configured in a Router's TokenAdminRegistry in a single scan; + * pagination is not supported. + * + * @throws {@link CCTParamsInvalidError} If `address` is not a valid Solana public key. + * @throws {@link CCIPContractNotRouterError} If `address` does not resolve to a Router. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const tokens = await cct.getSupportedTokens({ address: router }) + * ``` + */ + getSupportedTokens(opts: GetSupportedTokensParams): Promise { + return this.#getSupportedTokens.query(this.chain, opts) + } + + /** + * Serializes an unsigned Solana CCT tx for external signing. + * + * @throws {@link CCTParamsInvalidError} If `encoding` is unsupported or the transaction uses + * address lookup tables, which legacy-message serialization cannot represent. + * + * @example + * ```ts + * const cct = SolanaTokenManager.fromChain(chain) + * const unsigned = await cct.generateUnsignedSetPool({ ...params, payer }) + * const base58 = await cct.serializeUnsignedTx(unsigned, payer) + * const base64 = await cct.serializeUnsignedTx(unsigned, payer, 'base64') + * ``` + */ + serializeUnsignedTx( + unsigned: Pick, + payer: string, + encoding?: SerializedSolanaTxEncoding, + ): Promise { + return serializeUnsignedSolanaTx(this.provider, unsigned, payer, encoding) + } +} + +export * from '../errors.ts' +export { + type TokenPoolType, + TOKEN_POOL_PROGRAMS, + deriveTokenPoolSignerPda, + resolveTokenPoolProgram, +} from './programs/token-pool.ts' +export { TOKEN_AUTHORITY_TYPES } from './token/constants.ts' +export { DEFAULT_WRITABLE_INDEXES, REGISTRATION_METHODS } from './token-admin-registry/constants.ts' +export type { TransactionResult } from '../operation.ts' +export type { SerializedSolanaTxEncoding } from './serialize.ts' +export type * from './token/operations/index.ts' +export type * from './token-pool/operations/index.ts' +export type * from './token-admin-registry/operations/index.ts' diff --git a/ccip-sdk/src/cct/solana/operation.test.ts b/ccip-sdk/src/cct/solana/operation.test.ts new file mode 100644 index 000000000..abbb211fb --- /dev/null +++ b/ccip-sdk/src/cct/solana/operation.test.ts @@ -0,0 +1,126 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey } from '@solana/web3.js' + +import { CCIPWalletInvalidError } from '../../errors/index.ts' +import { ChainFamily } from '../../networks.ts' +import type { SolanaChain } from '../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../solana/types.ts' +import { SolanaOperation } from './operation.ts' + +class TestOperation extends SolanaOperation<{ value: string }> { + readonly name = 'testOperation' + captured?: string + validated?: string + + protected override validate(params: { payer: string }): void { + this.validated = params.payer + } + + protected buildUnsigned( + _chain: SolanaChain, + params: { payer: string; value: string }, + ): Promise { + this.captured = params.payer + return Promise.resolve({ family: ChainFamily.Solana, instructions: [] }) + } +} + +class ParsedTestOperation extends SolanaOperation< + { value: string }, + UnsignedSolanaTx, + { payer: string; value: number } +> { + readonly name = 'parsedTestOperation' + readonly lifecycle: string[] = [] + captured?: { payer: string; value: number } + + protected override validate(params: { payer: string; value: string }): void { + this.lifecycle.push(`validate:${params.value}`) + } + + protected override parse(params: { payer: string; value: string }): { + payer: string + value: number + } { + this.lifecycle.push(`parse:${params.value}`) + return { ...params, value: Number(params.value) } + } + + protected buildUnsigned( + _chain: SolanaChain, + params: { payer: string; value: number }, + ): Promise { + this.lifecycle.push(`build:${params.value}`) + this.captured = params + return Promise.resolve({ family: ChainFamily.Solana, instructions: [] }) + } +} + +const chain = { logger: console, connection: {} } as unknown as SolanaChain + +describe('SolanaOperation', () => { + it('validates, parses, then builds without mutating input', async () => { + const op = new ParsedTestOperation() + const params = { payer: PublicKey.default.toBase58(), value: '42' } + + await op.generate(chain, params) + + assert.deepEqual(op.lifecycle, ['validate:42', 'parse:42', 'build:42']) + assert.deepEqual(op.captured, { payer: params.payer, value: 42 }) + assert.equal(params.value, '42') + }) + + it('stops before parsing or building when validation fails', async () => { + class RejectingOperation extends ParsedTestOperation { + protected override validate(params: { payer: string; value: string }): void { + this.lifecycle.push(`validate:${params.value}`) + throw new Error('invalid params') + } + } + + const op = new RejectingOperation() + + await assert.rejects(() => op.generate(chain, { payer: 'payer', value: '42' })) + assert.deepEqual(op.lifecycle, ['validate:42']) + }) + + it('uses wallet public key as payer without mutating caller params', async () => { + const op = new TestOperation() + const wallet = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, + } + const params = { value: 'x', payer: PublicKey.default.toBase58(), wallet } + + await op.execute(chain, params) + + assert.equal(op.validated, wallet.publicKey.toBase58()) + assert.equal(op.captured, wallet.publicKey.toBase58()) + assert.equal(params.payer, PublicKey.default.toBase58()) + }) + + it('does not require payer on signed execution params', async () => { + const op = new TestOperation() + const wallet = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, + } + + await op.execute(chain, { value: 'x', wallet }) + + assert.equal(op.captured, wallet.publicKey.toBase58()) + }) + + it('rejects invalid wallets before validation or building unsigned txs', async () => { + const op = new TestOperation() + + await assert.rejects( + () => op.execute(chain, { value: 'x', wallet: {} }), + (err: unknown) => err instanceof CCIPWalletInvalidError, + ) + assert.equal(op.validated, undefined) + assert.equal(op.captured, undefined) + }) +}) diff --git a/ccip-sdk/src/cct/solana/operation.ts b/ccip-sdk/src/cct/solana/operation.ts new file mode 100644 index 000000000..59698b737 --- /dev/null +++ b/ccip-sdk/src/cct/solana/operation.ts @@ -0,0 +1,61 @@ +/** + * Solana {@link Operation} lifecycle: prepare (validate → parse) → build unsigned tx → submit. + * Default execution uses wallet.publicKey as payer; use generateUnsigned* for a custom payer. + * + * @packageDocumentation + */ + +import { CCIPWalletInvalidError } from '../../errors/index.ts' +import type { SolanaChain } from '../../solana/index.ts' +import { type UnsignedSolanaTx, isWallet } from '../../solana/types.ts' +import { type TransactionResult, Operation } from '../operation.ts' +import { submit } from './submit.ts' + +/** Unsigned Solana operation params include an explicit fee payer. */ +export type SolanaGenerateParams

= P & { payer: string } + +/** Signed Solana operation params derive payer from `wallet.publicKey`. */ +export type SolanaExecuteParams

= P & { + wallet: unknown + computeUnits?: number +} + +/** + * Solana CCT write base. Subclasses supply {@link parse} and {@link buildUnsigned}. + * + * Override {@link parse} for validation, defaults, or conversion; it must be overridden whenever + * `Parsed` differs from `SolanaGenerateParams

`. + */ +export abstract class SolanaOperation< + P extends object, + Tx extends UnsignedSolanaTx = UnsignedSolanaTx, + Parsed = SolanaGenerateParams

, +> extends Operation, Tx, TransactionResult, Parsed> { + /** Build instructions from validated, parsed params. */ + protected abstract buildUnsigned(chain: SolanaChain, params: Parsed): Promise + + /** Run {@link prepare} and {@link buildUnsigned}; no signing. */ + async generate(chain: SolanaChain, params: SolanaGenerateParams

): Promise { + return this.buildUnsigned(chain, this.prepare(params)) + } + + /** Validates the wallet and prepares signed execution parameters with it as payer. */ + protected prepareWalletExecution(params: SolanaExecuteParams

) { + const { wallet, computeUnits, ...rest } = params + if (!isWallet(wallet)) throw new CCIPWalletInvalidError(wallet) + + const payer = wallet.publicKey + return { + wallet, + payer, + computeUnits, + parsed: this.prepare({ ...rest, payer: payer.toBase58() } as SolanaGenerateParams

), + } + } + + /** Generate, sign, simulate, send, and confirm with wallet.publicKey as payer. */ + async execute(chain: SolanaChain, params: SolanaExecuteParams

): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/programs/alt.ts b/ccip-sdk/src/cct/solana/programs/alt.ts new file mode 100644 index 000000000..a214149e8 --- /dev/null +++ b/ccip-sdk/src/cct/solana/programs/alt.ts @@ -0,0 +1,101 @@ +import { Buffer } from 'buffer' + +import { getAssociatedTokenAddressSync } from '@solana/spl-token' +import { + AddressLookupTableProgram, + PublicKey, + SystemProgram, + TransactionInstruction, +} from '@solana/web3.js' + +import type { SolanaChain } from '../../../solana/index.ts' +import { resolveTokenProgram } from '../../../solana/utils.ts' +import { deriveFeeBillingTokenConfigPda } from './fee-quoter.ts' +import { deriveExternalTokenPoolsSignerPda, deriveTokenAdminRegistryPda } from './router.ts' +import { deriveTokenPoolConfigPda, deriveTokenPoolSignerPda } from './token-pool.ts' + +const CREATE_LOOKUP_TABLE_DISCRIMINATOR = 0 +const CREATE_LOOKUP_TABLE_DATA_LENGTH = 13 + +type DeriveCcipLookupTableAddressesParams = { + lookupTableAddress: PublicKey + tokenMint: PublicKey + poolProgram: PublicKey +} + +type BuildCreateLookupTableInstructionParams = { + authority: PublicKey + payer: PublicKey + recentSlot: number | bigint +} + +type BuildCreateLookupTableInstructionResult = { + instruction: TransactionInstruction + lookupTableAddress: PublicKey +} + +/** Builds an ALT create instruction without requiring the authority signature. */ +export function buildCreateLookupTableInstruction({ + authority, + payer, + recentSlot, +}: BuildCreateLookupTableInstructionParams): BuildCreateLookupTableInstructionResult { + const recentSlotBigInt = BigInt(recentSlot) + const recentSlotBuffer = Buffer.alloc(8) + recentSlotBuffer.writeBigUInt64LE(recentSlotBigInt) + + const [lookupTableAddress, bump] = PublicKey.findProgramAddressSync( + [authority.toBuffer(), recentSlotBuffer], + AddressLookupTableProgram.programId, + ) + + const data = Buffer.alloc(CREATE_LOOKUP_TABLE_DATA_LENGTH) + data.writeUInt32LE(CREATE_LOOKUP_TABLE_DISCRIMINATOR, 0) + data.writeBigUInt64LE(recentSlotBigInt, 4) + data.writeUInt8(bump, 12) + + return { + lookupTableAddress, + instruction: new TransactionInstruction({ + programId: AddressLookupTableProgram.programId, + keys: [ + { pubkey: lookupTableAddress, isSigner: false, isWritable: true }, + { pubkey: authority, isSigner: false, isWritable: false }, + { pubkey: payer, isSigner: true, isWritable: true }, + { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, + ], + data, + }), + } +} + +/** Derives the standard CCIP token pool addresses stored in a pool lookup table. */ +export async function deriveCcipLookupTableAddresses( + chain: SolanaChain, + { lookupTableAddress, tokenMint, poolProgram }: DeriveCcipLookupTableAddressesParams, +): Promise { + const tokenProgram = await resolveTokenProgram(chain.connection, tokenMint) + const poolConfig = deriveTokenPoolConfigPda(poolProgram, tokenMint) + const { router: routerAddress } = await chain.getTokenPoolConfig(poolConfig.toBase58()) + const router = new PublicKey(routerAddress) + const { feeQuoter } = await chain._getRouterConfig(routerAddress) + + const tokenAdminRegistry = deriveTokenAdminRegistryPda(router, tokenMint) + const poolSigner = deriveTokenPoolSignerPda(poolProgram, tokenMint) + const poolTokenAta = getAssociatedTokenAddressSync(tokenMint, poolSigner, true, tokenProgram) + const feeTokenConfig = deriveFeeBillingTokenConfigPda(feeQuoter, tokenMint) + const routerPoolSigner = deriveExternalTokenPoolsSignerPda(router, poolProgram) + + return [ + lookupTableAddress, + tokenAdminRegistry, + poolProgram, + poolConfig, + poolTokenAta, + poolSigner, + tokenProgram, + tokenMint, + feeTokenConfig, + routerPoolSigner, + ] +} diff --git a/ccip-sdk/src/cct/solana/programs/fee-quoter.ts b/ccip-sdk/src/cct/solana/programs/fee-quoter.ts new file mode 100644 index 000000000..71b1dd8a0 --- /dev/null +++ b/ccip-sdk/src/cct/solana/programs/fee-quoter.ts @@ -0,0 +1,11 @@ +import { Buffer } from 'buffer' + +import { PublicKey } from '@solana/web3.js' + +/** Derives the FeeQuoter billing token config PDA for a mint. */ +export function deriveFeeBillingTokenConfigPda(feeQuoter: PublicKey, mint: PublicKey): PublicKey { + return PublicKey.findProgramAddressSync( + [Buffer.from('fee_billing_token_config'), mint.toBuffer()], + feeQuoter, + )[0] +} diff --git a/ccip-sdk/src/cct/solana/programs/router.ts b/ccip-sdk/src/cct/solana/programs/router.ts new file mode 100644 index 000000000..7df7abc3e --- /dev/null +++ b/ccip-sdk/src/cct/solana/programs/router.ts @@ -0,0 +1,37 @@ +import { Buffer } from 'buffer' + +import { Program } from '@coral-xyz/anchor' +import { PublicKey } from '@solana/web3.js' + +import { IDL as CCIP_ROUTER_IDL } from '../../../solana/idl/1.6.0/CCIP_ROUTER.ts' +import type { SolanaChain } from '../../../solana/index.ts' +import { simulationProvider } from '../../../solana/utils.ts' + +/** Creates an Anchor Program client for the CCIP Router program. */ +export function createRouterProgram(chain: SolanaChain, router: PublicKey, payer: PublicKey) { + return new Program(CCIP_ROUTER_IDL, router, simulationProvider(chain, payer)) +} + +/** Derives the Router config PDA. */ +export function deriveRouterConfigPda(router: PublicKey): PublicKey { + return PublicKey.findProgramAddressSync([Buffer.from('config')], router)[0] +} + +/** Derives the Router token admin registry PDA for a mint. */ +export function deriveTokenAdminRegistryPda(router: PublicKey, mint: PublicKey): PublicKey { + return PublicKey.findProgramAddressSync( + [Buffer.from('token_admin_registry'), mint.toBuffer()], + router, + )[0] +} + +/** Derives the Router external token pools signer PDA for a pool program. */ +export function deriveExternalTokenPoolsSignerPda( + router: PublicKey, + poolProgram: PublicKey, +): PublicKey { + return PublicKey.findProgramAddressSync( + [Buffer.from('external_token_pools_signer'), poolProgram.toBuffer()], + router, + )[0] +} diff --git a/ccip-sdk/src/cct/solana/programs/token-pool.ts b/ccip-sdk/src/cct/solana/programs/token-pool.ts new file mode 100644 index 000000000..f58ea8a27 --- /dev/null +++ b/ccip-sdk/src/cct/solana/programs/token-pool.ts @@ -0,0 +1,159 @@ +import { Buffer } from 'buffer' + +import { Program } from '@coral-xyz/anchor' +import { PublicKey } from '@solana/web3.js' + +import { CCIPError } from '../../../errors/index.ts' +import { + type TokenPoolConfig, + LOCK_RELEASE_TOKEN_POOL_IDL, + TOKEN_POOL_IDL, + tokenPoolCoder, +} from '../../../solana/idl/token-pool-coder.ts' +export type { TokenPoolConfig } from '../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../solana/index.ts' +import { simulationProvider } from '../../../solana/utils.ts' +import { CCTDataDecodeError } from '../../errors.ts' + +/** Canonical Solana token pool program addresses. */ +export const TOKEN_POOL_PROGRAMS = { + 'burn-mint': '41FGToCmdaWa1dgZLKFAjvmx6e6AjVTX7SVRibvsMGVB', + 'lock-release': '8eqh8wppT9c5rw4ERqNCffvU6cNFJWff9WmkcYtmGiqC', +} as const + +/** Canonical Solana token pool program type. */ +export type TokenPoolType = keyof typeof TOKEN_POOL_PROGRAMS + +/** Identifies a canonical burn-mint token pool program. */ +export type BurnMintPoolProgramRef = { + poolType: 'burn-mint' + poolProgramAddress?: never +} + +/** Identifies a canonical lock-release token pool program. */ +export type LockReleasePoolProgramRef = { + poolType: 'lock-release' + poolProgramAddress?: never +} + +/** Identifies a custom token pool program. */ +export type CustomPoolProgramRef = { + poolProgramAddress: string + poolType?: never +} + +/** Identifies a canonical token pool or a custom pool program. */ +export type PoolProgramRef = + | BurnMintPoolProgramRef + | LockReleasePoolProgramRef + | CustomPoolProgramRef + +type TokenPoolStateDecodeContext = { + tokenPool: string + mint: string + poolProgram: string + accountOwner: string +} + +/** + * Resolves a canonical token pool program type to its address. + * + * @example + * ```ts + * const poolProgram = resolveTokenPoolProgram('burn-mint') + * ``` + */ +export function resolveTokenPoolProgram(poolType: TokenPoolType): PublicKey { + return new PublicKey(TOKEN_POOL_PROGRAMS[poolType]) +} + +/** Creates an Anchor Program client for a burn-mint token pool program. */ +export function createTokenPoolProgram( + chain: SolanaChain, + poolProgram: PublicKey, + payer: PublicKey, +) { + return new Program(TOKEN_POOL_IDL, poolProgram, simulationProvider(chain, payer)) +} + +/** Creates an Anchor Program client for a lock-release token pool program. */ +export function createLockReleaseTokenPoolProgram( + chain: SolanaChain, + poolProgram: PublicKey, + payer: PublicKey, +) { + return new Program(LOCK_RELEASE_TOKEN_POOL_IDL, poolProgram, simulationProvider(chain, payer)) +} + +/** Decodes a canonical token pool state account. */ +export function decodeTokenPoolState( + data: Buffer, + context: TokenPoolStateDecodeContext, +): { version: number; config: TokenPoolConfig } { + try { + return tokenPoolCoder.accounts.decode<{ version: number; config: TokenPoolConfig }>( + 'state', + data, + ) + } catch (cause) { + throw new CCTDataDecodeError(context.tokenPool, { + cause: cause instanceof Error ? cause : CCIPError.from(cause), + context: { + mint: context.mint, + poolProgram: context.poolProgram, + accountOwner: context.accountOwner, + }, + }) + } +} + +/** Derives the token pool global config PDA. */ +export function deriveTokenPoolGlobalConfigPda(poolProgram: PublicKey): PublicKey { + return PublicKey.findProgramAddressSync([Buffer.from('config')], poolProgram)[0] +} + +/** Derives a token pool state/config PDA for a mint. */ +export function deriveTokenPoolConfigPda(poolProgram: PublicKey, mint: PublicKey): PublicKey { + return PublicKey.findProgramAddressSync( + [Buffer.from('ccip_tokenpool_config'), mint.toBuffer()], + poolProgram, + )[0] +} + +/** + * Derives a token pool signer PDA for a mint. + * + * @example + * ```ts + * const poolProgram = resolveTokenPoolProgram('burn-mint') + * const poolSigner = deriveTokenPoolSignerPda(poolProgram, new PublicKey(tokenAddress)) + * ``` + */ +export function deriveTokenPoolSignerPda(poolProgram: PublicKey, mint: PublicKey): PublicKey { + return PublicKey.findProgramAddressSync( + [Buffer.from('ccip_tokenpool_signer'), mint.toBuffer()], + poolProgram, + )[0] +} + +/** Derives a token pool chain configuration PDA. */ +export function deriveTokenPoolChainConfigPda( + poolProgram: PublicKey, + remoteChainSelector: bigint, + mint: PublicKey, +): PublicKey { + const selector = Buffer.alloc(8) + selector.writeBigUInt64LE(remoteChainSelector) + return PublicKey.findProgramAddressSync( + [Buffer.from('ccip_tokenpool_chainconfig'), selector, mint.toBuffer()], + poolProgram, + )[0] +} + +/** Derives the token pool program data PDA. */ +export function deriveTokenPoolProgramDataPda(poolProgram: PublicKey): PublicKey { + return PublicKey.findProgramAddressSync( + [poolProgram.toBuffer()], + new PublicKey('BPFLoaderUpgradeab1e11111111111111111111111'), + )[0] +} diff --git a/ccip-sdk/src/cct/solana/programs/token.ts b/ccip-sdk/src/cct/solana/programs/token.ts new file mode 100644 index 000000000..0e7f93eb7 --- /dev/null +++ b/ccip-sdk/src/cct/solana/programs/token.ts @@ -0,0 +1,11 @@ +import { PublicKey } from '@solana/web3.js' + +import { METADATA_PROGRAM_ID } from '../token/constants.ts' + +/** Derives the Metaplex metadata PDA for a mint. */ +export function deriveMetadataAddress(mint: PublicKey): PublicKey { + return PublicKey.findProgramAddressSync( + [Buffer.from('metadata'), METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer()], + METADATA_PROGRAM_ID, + )[0] +} diff --git a/ccip-sdk/src/cct/solana/query.ts b/ccip-sdk/src/cct/solana/query.ts new file mode 100644 index 000000000..4ee775701 --- /dev/null +++ b/ccip-sdk/src/cct/solana/query.ts @@ -0,0 +1,17 @@ +/** + * Solana CCT reads: {@link Query} bound to a {@link SolanaChain}. The read-only counterpart of + * {@link SolanaOperation} — no wallet, no instructions, no submit. + * + * @packageDocumentation + */ + +import type { SolanaChain } from '../../solana/index.ts' +import { Query } from '../query.ts' + +/** Shared base for read-only Solana CCT queries; see {@link Query}. */ +export abstract class SolanaQuery

extends Query< + SolanaChain, + P, + R, + Parsed +> {} diff --git a/ccip-sdk/src/cct/solana/serialize.test.ts b/ccip-sdk/src/cct/solana/serialize.test.ts new file mode 100644 index 000000000..e9084ed40 --- /dev/null +++ b/ccip-sdk/src/cct/solana/serialize.test.ts @@ -0,0 +1,56 @@ +import { Buffer } from 'buffer' +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Message, PublicKey, SystemProgram, TransactionInstruction } from '@solana/web3.js' +import bs58 from 'bs58' + +import { CCTParamsInvalidError } from '../errors.ts' +import { serializeUnsignedSolanaTx } from './serialize.ts' + +const KEY = PublicKey.default +const connection = { + getLatestBlockhash: async () => ({ blockhash: KEY.toBase58(), lastValidBlockHeight: 0 }), +} +const unsigned = { + instructions: [ + new TransactionInstruction({ + programId: SystemProgram.programId, + keys: [], + data: Buffer.alloc(0), + }), + ], +} + +describe('Serialize (cct/solana)', () => { + it('serializes unsigned Solana txs as legacy messages in supported encodings', async () => { + const base58 = await serializeUnsignedSolanaTx(connection, unsigned, KEY) + const base64 = await serializeUnsignedSolanaTx(connection, unsigned, KEY, 'base64') + const hex = await serializeUnsignedSolanaTx(connection, unsigned, KEY, 'hex') + + assert.ok(Message.from(bs58.decode(base58))) + assert.ok(Message.from(Buffer.from(base64, 'base64'))) + assert.ok(Message.from(Buffer.from(hex, 'hex'))) + }) + + it('rejects lookup tables for legacy message serialization', async () => { + await assert.rejects( + () => + serializeUnsignedSolanaTx(connection, { ...unsigned, lookupTables: [{} as never] }, KEY), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'serializeUnsignedTx' && + err.context.param === 'lookupTables', + ) + }) + + it('rejects unsupported transaction encodings', async () => { + await assert.rejects( + () => serializeUnsignedSolanaTx(connection, unsigned, KEY, 'base32'), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'serializeUnsignedTx' && + err.context.param === 'encoding', + ) + }) +}) diff --git a/ccip-sdk/src/cct/solana/serialize.ts b/ccip-sdk/src/cct/solana/serialize.ts new file mode 100644 index 000000000..3bb811254 --- /dev/null +++ b/ccip-sdk/src/cct/solana/serialize.ts @@ -0,0 +1,48 @@ +import { Buffer } from 'buffer' + +import { PublicKey, TransactionMessage } from '@solana/web3.js' +import bs58 from 'bs58' + +import type { UnsignedSolanaTx } from '../../solana/types.ts' +import { CCTParamsInvalidError } from '../errors.ts' + +/** Supported serialized transaction encodings. */ +export type SerializedSolanaTxEncoding = 'base58' | 'base64' | 'hex' + +/** Serializes an unsigned Solana tx into one legacy message for external signing. */ +export async function serializeUnsignedSolanaTx( + connection: { getLatestBlockhash: () => Promise<{ blockhash: string }> }, + unsigned: Pick, + payer: PublicKey | string, + encoding = 'base58', +): Promise { + if (unsigned.lookupTables?.length) { + throw new CCTParamsInvalidError( + 'serializeUnsignedTx', + 'lookupTables', + 'legacy-message serialization does not support address lookup tables', + ) + } + + const payerKey = typeof payer === 'string' ? new PublicKey(payer) : payer + const { blockhash } = await connection.getLatestBlockhash() + const serialized = Buffer.from( + new TransactionMessage({ + payerKey, + recentBlockhash: blockhash, + instructions: unsigned.instructions, + }) + .compileToLegacyMessage() + .serialize(), + ) + + if (encoding === 'base58') return bs58.encode(serialized) + if (encoding === 'base64') return serialized.toString('base64') + if (encoding === 'hex') return serialized.toString('hex') + + throw new CCTParamsInvalidError( + 'serializeUnsignedTx', + 'encoding', + `unsupported Solana transaction encoding: ${String(encoding)}`, + ) +} diff --git a/ccip-sdk/src/cct/solana/submit.test.ts b/ccip-sdk/src/cct/solana/submit.test.ts new file mode 100644 index 000000000..d3c4e6ecf --- /dev/null +++ b/ccip-sdk/src/cct/solana/submit.test.ts @@ -0,0 +1,74 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { SendTransactionError, TransactionExpiredTimeoutError } from '@solana/web3.js' + +import { CCTTxFailedError, CCTTxNotConfirmedError } from '../errors.ts' +import { createCCTSubmitError } from './submit.ts' + +const OP = 'setPool' + +describe('Submit error mapping (cct/solana)', () => { + it('maps post-broadcast confirmation errors with a signature to not-confirmed', () => { + const cause = Object.assign(new Error('transaction was not confirmed'), { signature: 'abc' }) + const err = createCCTSubmitError(OP, cause) + + assert.ok(err instanceof CCTTxNotConfirmedError) + assert.equal(err.isTransient, true) + assert.equal(err.context.txHash, 'abc') + }) + + it('maps web3.js transaction expiry errors to not-confirmed', () => { + const err = createCCTSubmitError(OP, new TransactionExpiredTimeoutError('def', 30)) + + assert.ok(err instanceof CCTTxNotConfirmedError) + assert.equal(err.context.txHash, 'def') + }) + + it('maps SendTransactionError with a signature to not-confirmed', () => { + const cause = new SendTransactionError({ + action: 'send', + signature: 'ghi', + transactionMessage: 'block height exceeded', + }) + const err = createCCTSubmitError(OP, cause) + + assert.ok(err instanceof CCTTxNotConfirmedError) + assert.equal(err.context.txHash, 'ghi') + }) + + it('maps signed on-chain failures to permanent tx failed', () => { + const cause = Object.assign(new Error('custom program error: 0x1'), { signature: 'jkl' }) + const err = createCCTSubmitError(OP, cause) + + assert.ok(err instanceof CCTTxFailedError) + assert.equal(err.isTransient, false) + assert.equal(err.context.txHash, undefined) + }) + + it('maps SendTransactionError with an empty signature to transient tx failed', () => { + const cause = new SendTransactionError({ + action: 'simulate', + signature: '', + transactionMessage: 'blockhash not found', + }) + const err = createCCTSubmitError(OP, cause) + + assert.ok(err instanceof CCTTxFailedError) + assert.equal(err.isTransient, true) + }) + + it('maps pre-broadcast transient errors to transient tx failed', () => { + const err = createCCTSubmitError(OP, new Error('blockhash not found')) + + assert.ok(err instanceof CCTTxFailedError) + assert.equal(err.isTransient, true) + }) + + it('maps program errors to permanent tx failed', () => { + const err = createCCTSubmitError(OP, new Error('custom program error: 0x1')) + + assert.ok(err instanceof CCTTxFailedError) + assert.equal(err.isTransient, false) + }) +}) diff --git a/ccip-sdk/src/cct/solana/submit.ts b/ccip-sdk/src/cct/solana/submit.ts new file mode 100644 index 000000000..d35e3a84a --- /dev/null +++ b/ccip-sdk/src/cct/solana/submit.ts @@ -0,0 +1,80 @@ +/** + * Shared sign-and-submit pipeline for Solana CCT operations. Maps simulation/program + * failures to permanent {@link CCTTxFailedError}, pre-broadcast infra failures to + * transient {@link CCTTxFailedError}, and post-broadcast confirmation failures to + * {@link CCTTxNotConfirmedError}. + * + * @packageDocumentation + */ + +import { + TransactionExpiredBlockheightExceededError, + TransactionExpiredNonceInvalidError, + TransactionExpiredTimeoutError, +} from '@solana/web3.js' + +import { CCIPWalletInvalidError, shouldRetry } from '../../errors/index.ts' +import type { SolanaChain } from '../../solana/index.ts' +import { type UnsignedSolanaTx, isWallet } from '../../solana/types.ts' +import { simulateAndSendTxs } from '../../solana/utils.ts' +import { CCTTxFailedError, CCTTxNotConfirmedError } from '../errors.ts' +import type { TransactionResult } from '../operation.ts' + +/** Signs, simulates, sends, and confirms a Solana CCT transaction. */ +export async function submit( + chain: SolanaChain, + wallet: unknown, + unsigned: UnsignedSolanaTx, + operation: string, + computeUnits?: number, +): Promise { + if (!isWallet(wallet)) throw new CCIPWalletInvalidError(wallet) + + try { + return { hash: await simulateAndSendTxs(chain, wallet, unsigned, computeUnits) } + } catch (error) { + throw createCCTSubmitError(operation, error) + } +} + +/** Maps Solana submit errors to permanent failed vs transient failed/not-confirmed CCT errors. */ +export function createCCTSubmitError( + operation: string, + error: unknown, +): CCTTxFailedError | CCTTxNotConfirmedError { + const signature = getSignature(error) + if (signature && isNotConfirmedError(error)) { + return new CCTTxNotConfirmedError(operation, signature, { + cause: error instanceof Error ? error : undefined, + }) + } + + return new CCTTxFailedError(operation, getReason(error), { + cause: error instanceof Error ? error : undefined, + isTransient: isTransientSubmitError(error), + }) +} + +function isTransientSubmitError(error: unknown): boolean { + return /blockhash|expired/i.test(getReason(error)) || shouldRetry(error) +} + +function isNotConfirmedError(error: unknown): boolean { + return ( + error instanceof TransactionExpiredBlockheightExceededError || + error instanceof TransactionExpiredNonceInvalidError || + error instanceof TransactionExpiredTimeoutError || + /not confirmed|unknown if it succeeded|block height exceeded/i.test(getReason(error)) + ) +} + +function getReason(error: unknown): string { + return error instanceof Error ? error.message : String(error) +} + +function getSignature(error: unknown): string | undefined { + if (!error || typeof error !== 'object' || !('signature' in error)) return undefined + return typeof error.signature === 'string' && error.signature.length > 0 + ? error.signature + : undefined +} diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/constants.ts b/ccip-sdk/src/cct/solana/token-admin-registry/constants.ts new file mode 100644 index 000000000..93341423e --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/constants.ts @@ -0,0 +1,11 @@ +/** Authorization paths used to register a token in the TokenAdminRegistry. */ +export const REGISTRATION_METHODS = { + OWNER: 'owner', + CCIP_ADMIN: 'ccip-admin', +} as const + +/** + * Positions of `poolConfig` (3), `poolTokenAta` (4), and `tokenMint` (7) in the pool ALT built + * by `createLookupTable`. Custom pools must extend this, e.g. `[...DEFAULT_WRITABLE_INDEXES, n]`. + */ +export const DEFAULT_WRITABLE_INDEXES = [3, 4, 7] as const diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/accept-admin.test.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/accept-admin.test.ts new file mode 100644 index 000000000..cdb295878 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/accept-admin.test.ts @@ -0,0 +1,188 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey } from '@solana/web3.js' + +import { CCIPWalletInvalidError } from '../../../../errors/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveRouterConfigPda, deriveTokenAdminRegistryPda } from '../../programs/router.ts' +import { type GenerateAcceptAdminParams, AcceptAdmin } from './accept-admin.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const ADDRESS = Keypair.generate().publicKey.toBase58() +const ROUTER = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const PENDING_ADMIN = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: new PublicKey(PENDING_ADMIN), + signTransaction: async (tx: T) => tx, +} + +function stubChain( + pendingAdministrator = PENDING_ADMIN, + onAddress?: (address: string) => void, +): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: {}, + getTokenAdminRegistryFor: async (address: string) => { + onAddress?.(address) + return ROUTER + }, + getRegistryTokenConfig: async () => ({ administrator: PAYER, pendingAdministrator }), + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(stubChain(), { + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts: Partial = {}) { + return new AcceptAdmin().generate(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + payer: PAYER, + authority: PENDING_ADMIN, + ...opts, + }) +} + +describe('AcceptAdmin (cct/solana)', () => { + describe('generate', () => { + it('builds an unsigned accept admin instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + + assert.ok(instruction) + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction.programId.toBase58(), ROUTER) + assert.equal(instruction.data.toString('hex'), '6af010ad89d5a3f6') + assert.deepEqual( + instruction.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveRouterConfigPda(new PublicKey(ROUTER)).toBase58(), + isSigner: false, + isWritable: false, + }, + { + pubkey: deriveTokenAdminRegistryPda( + new PublicKey(ROUTER), + new PublicKey(TOKEN), + ).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: TOKEN, isSigner: false, isWritable: false }, + { pubkey: PENDING_ADMIN, isSigner: true, isWritable: true }, + ], + ) + }) + + it('resolves the router from address', async () => { + let requestedAddress: string | undefined + await new AcceptAdmin().generate( + stubChain(PENDING_ADMIN, (address) => (requestedAddress = address)), + { + tokenAddress: TOKEN, + address: ADDRESS, + payer: PAYER, + authority: PENDING_ADMIN, + }, + ) + + assert.equal(requestedAddress, ADDRESS) + }) + }) + + describe('validation', () => { + it('rejects an authority that is not the pending administrator', async () => { + await assert.rejects( + () => generate({ authority: PAYER }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'authority', + ) + }) + + it('rejects when no administrator is pending', async () => { + const noPendingChain = { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: {}, + getTokenAdminRegistryFor: async () => ROUTER, + getRegistryTokenConfig: async () => ({ administrator: PAYER }), + } as unknown as SolanaChain + + await assert.rejects( + () => + new AcceptAdmin().generate(noPendingChain, { + tokenAddress: TOKEN, + address: ADDRESS, + payer: PAYER, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'authority' && + typeof err.context.reason === 'string' && + err.context.reason.includes('no administrator is pending'), + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + assert.deepEqual( + await new AcceptAdmin().execute(submitChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + wallet: WALLET, + }), + { hash: HASH }, + ) + }) + + it('rejects an invalid wallet before generating instructions', async () => { + await assert.rejects( + new AcceptAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + wallet: {}, + }), + CCIPWalletInvalidError, + ) + }) + + it('requires the pending admin to be the executing wallet', async () => { + await assert.rejects( + () => + new AcceptAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + authority: PAYER, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'authority' && + typeof err.context.reason === 'string' && + err.context.reason.includes('requires authority to be the executing wallet'), + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/accept-admin.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/accept-admin.ts new file mode 100644 index 000000000..5b2762aff --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/accept-admin.ts @@ -0,0 +1,132 @@ +import { PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + createRouterProgram, + deriveRouterConfigPda, + deriveTokenAdminRegistryPda, +} from '../../programs/router.ts' +import { submit } from '../../submit.ts' +import { parsePublicKey, validateAuthorityMatchesWallet } from '../../validate.ts' + +/** Parameters shared by Solana TokenAdminRegistry `acceptAdmin` generation and execution. */ +type AcceptAdminParams = { + tokenAddress: string + /** + * CCIP contract to resolve the TokenAdminRegistry/Router from — a Router or OffRamp + * address works. + */ + address: string + /** Pending token admin. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type ParsedAcceptAdminParams = { + tokenAddress: PublicKey + address: PublicKey + payer: PublicKey + authority: PublicKey +} + +/** Parameters for unsigned Solana TokenAdminRegistry `acceptAdmin` generation. */ +export type GenerateAcceptAdminParams = SolanaGenerateParams + +/** Unsigned Solana TokenAdminRegistry `acceptAdmin` result. */ +export type GenerateAcceptAdminResult = UnsignedSolanaTx + +/** Parameters for executing Solana TokenAdminRegistry `acceptAdmin`. */ +export type ExecuteAcceptAdminParams = SolanaExecuteParams + +/** Result of executing Solana TokenAdminRegistry `acceptAdmin`. */ +export type ExecuteAcceptAdminResult = TransactionResult + +/** Accepts a pending TokenAdminRegistry administrator role. */ +export class AcceptAdmin extends SolanaOperation< + AcceptAdminParams, + UnsignedSolanaTx, + ParsedAcceptAdminParams +> { + readonly name = 'acceptAdmin' + + /** Parses public keys and defaults authority to payer without mutating caller params. */ + protected override parse(params: GenerateAcceptAdminParams): ParsedAcceptAdminParams { + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + address: parsePublicKey(this.name, 'address', params.address), + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + } + } + + /** Builds the unsigned instruction after confirming the caller is the pending admin. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedAcceptAdminParams, + ): Promise { + const { tokenAddress: tokenMint, payer, authority } = opts + const router = new PublicKey(await chain.getTokenAdminRegistryFor(opts.address.toBase58())) + const tokenConfig = await chain.getRegistryTokenConfig(router.toBase58(), tokenMint.toBase58()) + + if (!tokenConfig.pendingAdministrator) { + throw new CCTParamsInvalidError( + this.name, + 'authority', + `no administrator is pending for this token (current administrator: ${tokenConfig.administrator}) — nothing to accept`, + ) + } + if (!new PublicKey(tokenConfig.pendingAdministrator).equals(authority)) { + throw new CCTParamsInvalidError( + this.name, + 'authority', + 'must be the pending token administrator', + ) + } + + const instruction = await createRouterProgram(chain, router, payer) + .methods.acceptAdminRoleTokenAdminRegistry() + .accounts({ + config: deriveRouterConfigPda(router), + tokenAdminRegistry: deriveTokenAdminRegistryPda(router, tokenMint), + mint: tokenMint, + authority, + }) + .instruction() + + chain.logger.debug( + `${this.name}: router = ${router.toBase58()}, token = ${tokenMint.toBase58()}`, + ) + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the pending admin wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteAcceptAdminParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'acceptAdmin requires authority to be the executing wallet. Use generateUnsignedAcceptAdmin for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/append-to-lookup-table.test.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/append-to-lookup-table.test.ts new file mode 100644 index 000000000..68da8b831 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/append-to-lookup-table.test.ts @@ -0,0 +1,350 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { AddressLookupTableProgram, Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveCcipLookupTableAddresses } from '../../programs/alt.ts' +import { TOKEN_POOL_PROGRAMS } from '../../programs/token-pool.ts' +import { AppendToLookupTable } from './append-to-lookup-table.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const POOL_PROGRAM = Keypair.generate().publicKey.toBase58() +const ROUTER = Keypair.generate().publicKey.toBase58() +const FEE_QUOTER = Keypair.generate().publicKey +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const LOOKUP_TABLE = Keypair.generate().publicKey.toBase58() +const ALT_EXTEND_ADDRESSES_OFFSET = 12 // 4-byte discriminator + 8-byte address vector length +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: new PublicKey(AUTHORITY), + signTransaction: async (tx: T) => tx, +} + +type StubChainOptions = { + addresses?: PublicKey[] + authority?: string | null + onGetLookupTable?: () => void + missingLookupTable?: boolean +} + +function stubChain({ + addresses = [], + authority = AUTHORITY, + onGetLookupTable, + missingLookupTable = false, +}: StubChainOptions = {}): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + getAccountInfo: async () => ({ owner: TOKEN_PROGRAM_ID }), + getAddressLookupTable: async () => { + onGetLookupTable?.() + return { + value: missingLookupTable + ? null + : { + state: { + authority: authority ? new PublicKey(authority) : undefined, + addresses, + }, + }, + } + }, + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + getTokenPoolConfig: async () => ({ + token: TOKEN, + router: ROUTER, + tokenPoolProgram: POOL_PROGRAM, + }), + _getRouterConfig: async () => ({ feeQuoter: FEE_QUOTER }), + } as unknown as SolanaChain +} + +function generate(opts = {}, chain = stubChain()) { + return new AppendToLookupTable().generate(chain, { + lookupTableAddress: LOOKUP_TABLE, + payer: PAYER, + authority: AUTHORITY, + additionalAddresses: [Keypair.generate().publicKey.toBase58()], + ...opts, + }) +} + +describe('AppendToLookupTable (cct/solana)', () => { + describe('generate', () => { + it('builds extend ALT instructions', async () => { + const unsigned = await generate() + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(unsigned.instructions.length, 1) + assert.equal( + unsigned.instructions[0]!.programId.toBase58(), + AddressLookupTableProgram.programId.toBase58(), + ) + }) + + it('chunks additional addresses into multiple extend instructions', async () => { + const additionalAddresses = Array.from({ length: 31 }, () => + Keypair.generate().publicKey.toBase58(), + ) + const unsigned = await generate({ additionalAddresses }) + + assert.equal(unsigned.instructions.length, 2) + }) + + it('appends derived CCIP addresses before manual addresses', async () => { + const chain = stubChain() + const manualAddress = Keypair.generate().publicKey + const ccipAddresses = await deriveCcipLookupTableAddresses(chain, { + lookupTableAddress: new PublicKey(LOOKUP_TABLE), + tokenMint: new PublicKey(TOKEN), + poolProgram: new PublicKey(POOL_PROGRAM), + }) + const unsigned = await generate( + { + tokenAddress: TOKEN, + poolProgramAddress: POOL_PROGRAM, + additionalAddresses: [manualAddress.toBase58()], + }, + chain, + ) + const appendedAddresses = Array.from( + { length: ccipAddresses.length + 1 }, + (_, i) => + new PublicKey( + unsigned.instructions[0]!.data.subarray( + ALT_EXTEND_ADDRESSES_OFFSET + i * 32, + ALT_EXTEND_ADDRESSES_OFFSET + (i + 1) * 32, + ), + ), + ) + + assert.deepEqual( + appendedAddresses.map((address) => address.toBase58()), + [...ccipAddresses, manualAddress].map((address) => address.toBase58()), + ) + }) + + it('accepts a canonical pool type', async () => { + const unsigned = await generate({ + tokenAddress: TOKEN, + poolType: 'burn-mint', + }) + + assert.equal(unsigned.instructions.length, 1) + assert.ok( + unsigned.instructions[0]!.data.includes( + new PublicKey(TOKEN_POOL_PROGRAMS['burn-mint']).toBuffer(), + ), + ) + }) + + it('ignores an undefined unused pool reference', async () => { + const unsigned = await generate({ + tokenAddress: TOKEN, + poolProgramAddress: POOL_PROGRAM, + poolType: undefined, + }) + + assert.equal(unsigned.instructions.length, 1) + }) + + it('rejects auto-derived CCIP addresses when the canonical block already exists', async () => { + const chain = stubChain() + const ccipAddresses = await deriveCcipLookupTableAddresses(chain, { + lookupTableAddress: new PublicKey(LOOKUP_TABLE), + tokenMint: new PublicKey(TOKEN), + poolProgram: new PublicKey(POOL_PROGRAM), + }) + + await assert.rejects( + () => + generate( + { tokenAddress: TOKEN, poolProgramAddress: POOL_PROGRAM }, + stubChain({ addresses: ccipAddresses }), + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'appendToLookupTable' && + err.context.param === 'lookupTableAddress', + ) + }) + + it('rejects a partial canonical CCIP address block', async () => { + const ccipAddresses = await deriveCcipLookupTableAddresses(stubChain(), { + lookupTableAddress: new PublicKey(LOOKUP_TABLE), + tokenMint: new PublicKey(TOKEN), + poolProgram: new PublicKey(POOL_PROGRAM), + }) + + await assert.rejects( + () => + generate( + { tokenAddress: TOKEN, poolProgramAddress: POOL_PROGRAM }, + stubChain({ addresses: [ccipAddresses[0]!] }), + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'lookupTableAddress', + ) + }) + + it('defaults omitted additional addresses to an empty list', async () => { + const unsigned = await generate({ + additionalAddresses: undefined, + tokenAddress: TOKEN, + poolProgramAddress: POOL_PROGRAM, + }) + + assert.equal(unsigned.instructions.length, 1) + }) + + it('rejects authority mismatch', async () => { + await assert.rejects( + () => generate({}, stubChain({ authority: Keypair.generate().publicKey.toBase58() })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'appendToLookupTable' && + err.context.param === 'authority', + ) + }) + + it('rejects an ALT with no authority', async () => { + await assert.rejects( + () => generate({}, stubChain({ authority: null })), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'authority', + ) + }) + + it('rejects ALTs over 256 addresses', async () => { + const currentAddresses = Array.from({ length: 256 }, () => Keypair.generate().publicKey) + + await assert.rejects( + () => generate({}, stubChain({ addresses: currentAddresses })), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'appendToLookupTable' && + err.context.param === 'additionalAddresses', + ) + }) + }) + + describe('validation', () => { + it('rejects a missing lookup table', async () => { + await assert.rejects( + () => generate({}, stubChain({ missingLookupTable: true })), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'lookupTableAddress', + ) + }) + + it('rejects an ambiguous pool reference before the ALT RPC', async () => { + let getLookupTableCalls = 0 + + await assert.rejects( + new AppendToLookupTable().generate( + stubChain({ onGetLookupTable: () => getLookupTableCalls++ }), + { + lookupTableAddress: LOOKUP_TABLE, + payer: PAYER, + tokenAddress: TOKEN, + poolType: 'burn-mint', + poolProgramAddress: POOL_PROGRAM, + } as never, + ), + CCTParamsInvalidError, + ) + + assert.equal(getLookupTableCalls, 0) + }) + + it('rejects an invalid pool program address', async () => { + let getLookupTableCalls = 0 + + await assert.rejects( + new AppendToLookupTable().generate( + stubChain({ onGetLookupTable: () => getLookupTableCalls++ }), + { + lookupTableAddress: LOOKUP_TABLE, + payer: PAYER, + tokenAddress: TOKEN, + poolProgramAddress: 'invalid', + }, + ), + CCTParamsInvalidError, + ) + + assert.equal(getLookupTableCalls, 0) + }) + + it('rejects duplicate additional addresses', async () => { + const address = Keypair.generate().publicKey.toBase58() + await assert.rejects( + () => generate({ additionalAddresses: [address, address] }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'additionalAddresses', + ) + }) + + it('requires at least one address source', async () => { + await assert.rejects( + () => generate({ additionalAddresses: [] }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'appendToLookupTable' && + err.context.param === 'additionalAddresses', + ) + }) + + it('requires token and pool program together', async () => { + await assert.rejects( + () => generate({ tokenAddress: TOKEN }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'appendToLookupTable' && + err.context.param === 'tokenAddress', + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + assert.deepEqual( + await new AppendToLookupTable().execute(stubChain(), { + lookupTableAddress: LOOKUP_TABLE, + wallet: WALLET, + additionalAddresses: [Keypair.generate().publicKey.toBase58()], + }), + { hash: HASH }, + ) + }) + + it('rejects signed append when authority is not the wallet', async () => { + await assert.rejects( + () => + new AppendToLookupTable().execute(stubChain(), { + lookupTableAddress: LOOKUP_TABLE, + wallet: WALLET, + authority: PAYER, + additionalAddresses: [Keypair.generate().publicKey.toBase58()], + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'appendToLookupTable' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/append-to-lookup-table.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/append-to-lookup-table.ts new file mode 100644 index 000000000..a1e35fbf6 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/append-to-lookup-table.ts @@ -0,0 +1,246 @@ +import { + type PublicKey, + type TransactionInstruction, + AddressLookupTableProgram, +} from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { deriveCcipLookupTableAddresses } from '../../programs/alt.ts' +import type { PoolProgramRef } from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + parsePublicKey, + resolvePoolProgram, + validateAuthorityMatchesWallet, +} from '../../validate.ts' + +const MAX_ALT_ADDRESSES = 256 +const EXTEND_CHUNK_SIZE = 30 + +type AppendAdditionalAddressesParams = { + additionalAddresses: string[] + tokenAddress?: never + poolType?: never + poolProgramAddress?: never +} + +type AppendCanonicalAddressesParams = { + tokenAddress: string + additionalAddresses?: string[] +} & PoolProgramRef + +/** + * Parameters shared by Solana TokenAdminRegistry `appendToLookupTable` generation and execution. + * + * Provide `tokenAddress` with exactly one of `poolType` or `poolProgramAddress` to append the + * canonical CCIP addresses. Additional addresses may also be included. + * + * Otherwise, provide `additionalAddresses` only. + */ +type AppendToLookupTableParams = { + lookupTableAddress: string + /** ALT authority. Defaults to payer for unsigned generation and wallet public key for execute. */ + authority?: string +} & (AppendAdditionalAddressesParams | AppendCanonicalAddressesParams) + +/** Parameters for unsigned Solana lookup table append generation. */ +export type GenerateAppendToLookupTableParams = SolanaGenerateParams + +type ParsedAppendToLookupTableParams = { + payer: PublicKey + authority: PublicKey + lookupTableAddress: PublicKey + additionalAddresses: PublicKey[] + tokenMint?: PublicKey + poolProgram?: PublicKey +} + +/** Unsigned append lookup table result. */ +export type GenerateAppendToLookupTableResult = UnsignedSolanaTx + +/** Parameters for executing Solana TokenAdminRegistry `appendToLookupTable`. */ +export type ExecuteAppendToLookupTableParams = SolanaExecuteParams + +/** Result of executing Solana TokenAdminRegistry `appendToLookupTable`. */ +export type ExecuteAppendToLookupTableResult = TransactionResult + +/** Builds and submits Solana ALT extend instructions for token pool setup. */ +export class AppendToLookupTable extends SolanaOperation< + AppendToLookupTableParams, + GenerateAppendToLookupTableResult, + ParsedAppendToLookupTableParams +> { + readonly name = 'appendToLookupTable' + + /** Parses all public keys before any RPC. */ + protected override parse( + params: GenerateAppendToLookupTableParams, + ): ParsedAppendToLookupTableParams { + const payer = parsePublicKey(this.name, 'payer', params.payer) + const authority = + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority) + const lookupTableAddress = parsePublicKey( + this.name, + 'lookupTableAddress', + params.lookupTableAddress, + ) + + const hasTokenAddress = params.tokenAddress !== undefined + const hasPoolProgramAddress = params.poolProgramAddress !== undefined + const hasPoolProgram = params.poolType !== undefined || hasPoolProgramAddress + if (hasTokenAddress !== hasPoolProgram) { + throw new CCTParamsInvalidError( + this.name, + 'tokenAddress', + 'tokenAddress and exactly one of poolType or poolProgramAddress must be provided together', + ) + } + const tokenMint = + params.tokenAddress === undefined + ? undefined + : parsePublicKey(this.name, 'tokenAddress', params.tokenAddress) + const poolProgram = hasPoolProgram ? resolvePoolProgram(this.name, params) : undefined + const additionalAddresses = (params.additionalAddresses ?? []).map((address, i) => + parsePublicKey(this.name, `additionalAddresses[${i}]`, address), + ) + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (params.tokenAddress === undefined && !params.additionalAddresses?.length) { + throw new CCTParamsInvalidError( + this.name, + 'additionalAddresses', + 'must provide tokenAddress/poolProgramAddress or additionalAddresses', + ) + } + return { + payer, + authority, + lookupTableAddress, + additionalAddresses, + ...(tokenMint !== undefined && { tokenMint }), + ...(poolProgram !== undefined && { poolProgram }), + } + } + + /** Builds unsigned ALT extend instructions. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedAppendToLookupTableParams, + ): Promise { + const { payer, authority, lookupTableAddress, poolProgram } = opts + const lookupTable = await chain.connection.getAddressLookupTable(lookupTableAddress) + + if (!lookupTable.value) { + throw new CCTParamsInvalidError( + this.name, + 'lookupTableAddress', + `lookup table not found: ${lookupTableAddress.toBase58()}`, + ) + } + + if (!lookupTable.value.state.authority?.equals(authority)) { + throw new CCTParamsInvalidError( + this.name, + 'authority', + `authority mismatch; ALT authority is ${lookupTable.value.state.authority?.toBase58() ?? 'none'}`, + ) + } + + const existingAddresses = new Set( + lookupTable.value.state.addresses.map((address) => address.toBase58()), + ) + const addresses = [...opts.additionalAddresses] + + if (opts.tokenMint && poolProgram) { + const { tokenMint } = opts + const ccipAddresses = await deriveCcipLookupTableAddresses(chain, { + lookupTableAddress, + tokenMint, + poolProgram, + }) + if (ccipAddresses.some((address) => existingAddresses.has(address.toBase58()))) { + throw new CCTParamsInvalidError( + this.name, + 'lookupTableAddress', + 'lookup table already contains canonical CCIP addresses; only append additionalAddresses or use an empty ALT', + ) + } + + addresses.unshift(...ccipAddresses) + } + + const appendedAddresses = new Set() + for (const address of addresses) { + const value = address.toBase58() + if (existingAddresses.has(value) || appendedAddresses.has(value)) { + throw new CCTParamsInvalidError( + this.name, + 'additionalAddresses', + 'must not contain addresses already in the ALT or duplicate addresses', + ) + } + appendedAddresses.add(value) + } + + const totalAddressesAfterAppend = lookupTable.value.state.addresses.length + addresses.length + if (totalAddressesAfterAppend > MAX_ALT_ADDRESSES) { + throw new CCTParamsInvalidError( + this.name, + 'additionalAddresses', + `ALT cannot exceed ${MAX_ALT_ADDRESSES} addresses; requested ${totalAddressesAfterAppend}`, + ) + } + + const instructions: TransactionInstruction[] = [] + for (let i = 0; i < addresses.length; i += EXTEND_CHUNK_SIZE) { + instructions.push( + AddressLookupTableProgram.extendLookupTable({ + payer, + authority, + lookupTable: lookupTableAddress, + addresses: addresses.slice(i, i + EXTEND_CHUNK_SIZE), + }), + ) + } + + chain.logger.debug( + `${this.name}: lookupTable = ${lookupTableAddress.toBase58()}, appended = ${addresses.length}, total = ${totalAddressesAfterAppend}`, + ) + return { + family: ChainFamily.Solana, + instructions, + mainIndex: 0, + } + } + + /** Generate, sign, simulate, send, and confirm with wallet.publicKey as payer. */ + override async execute( + chain: SolanaChain, + params: ExecuteAppendToLookupTableParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'appendToLookupTable requires authority to be the executing wallet. Use generateUnsignedAppendToLookupTable for vault-owned ALTs and have the vault sign/execute it.', + ) + } + + const tx = await this.buildUnsigned(chain, parsed) + return submit(chain, wallet, tx, this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/create-lookup-table.test.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/create-lookup-table.test.ts new file mode 100644 index 000000000..f87446cdb --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/create-lookup-table.test.ts @@ -0,0 +1,207 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { AddressLookupTableProgram, Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { TOKEN_POOL_PROGRAMS } from '../../programs/token-pool.ts' +import { CreateLookupTable } from './create-lookup-table.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const POOL_PROGRAM = Keypair.generate().publicKey.toBase58() +const ROUTER = Keypair.generate().publicKey.toBase58() +const FEE_QUOTER = Keypair.generate().publicKey +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: new PublicKey(AUTHORITY), + signTransaction: async (tx: T) => tx, +} + +function stubChain(onGetSlot?: () => void): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + getSlot: async () => { + onGetSlot?.() + return 123 + }, + getAccountInfo: async () => ({ owner: TOKEN_PROGRAM_ID }), + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + getTokenPoolConfig: async () => ({ + token: TOKEN, + router: ROUTER, + tokenPoolProgram: POOL_PROGRAM, + }), + _getRouterConfig: async () => ({ feeQuoter: FEE_QUOTER }), + } as unknown as SolanaChain +} + +function generate(opts = {}) { + return new CreateLookupTable().generate(stubChain(), { + tokenAddress: TOKEN, + poolProgramAddress: POOL_PROGRAM, + payer: PAYER, + ...opts, + }) +} + +describe('CreateLookupTable (cct/solana)', () => { + describe('generate', () => { + it('builds create + extend ALT instructions', async () => { + const unsigned = await generate() + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(unsigned.instructions.length, 2) + assert.match(unsigned.lookupTableAddress, /^[1-9A-HJ-NP-Za-km-z]+$/) + assert.equal( + unsigned.instructions[0]!.programId.toBase58(), + AddressLookupTableProgram.programId.toBase58(), + ) + assert.equal( + unsigned.instructions[1]!.programId.toBase58(), + AddressLookupTableProgram.programId.toBase58(), + ) + assert.equal( + unsigned.instructions[0]!.keys.find((key) => key.pubkey.toBase58() === PAYER)?.isSigner, + false, + ) + }) + + it('accepts a canonical pool type', async () => { + const unsigned = await new CreateLookupTable().generate(stubChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + }) + + assert.equal(unsigned.instructions.length, 2) + assert.ok( + unsigned.instructions[1]!.data.includes( + new PublicKey(TOKEN_POOL_PROGRAMS['burn-mint']).toBuffer(), + ), + ) + }) + + it('builds create-only ALT instruction in createEmpty mode', async () => { + const unsigned = await new CreateLookupTable().generate(stubChain(), { + payer: PAYER, + authority: AUTHORITY, + mode: 'createEmpty', + }) + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(unsigned.instructions.length, 1) + assert.match(unsigned.lookupTableAddress, /^[1-9A-HJ-NP-Za-km-z]+$/) + assert.equal( + unsigned.instructions[0]!.programId.toBase58(), + AddressLookupTableProgram.programId.toBase58(), + ) + assert.equal( + unsigned.instructions[0]!.keys.find((key) => key.pubkey.toBase58() === AUTHORITY)?.isSigner, + false, + ) + }) + + it('defaults createEmpty authority to payer', async () => { + const unsigned = await new CreateLookupTable().generate(stubChain(), { + payer: PAYER, + mode: 'createEmpty', + }) + + assert.ok(unsigned.instructions[0]!.keys.some((key) => key.pubkey.toBase58() === PAYER)) + }) + + it('chunks additional addresses into multiple extend instructions', async () => { + const additionalAddresses = Array.from({ length: 21 }, () => + Keypair.generate().publicKey.toBase58(), + ) + const unsigned = await generate({ additionalAddresses }) + + assert.equal(unsigned.instructions.length, 3) + }) + + it('uses caller-provided authority', async () => { + const unsigned = await generate({ authority: AUTHORITY }) + + assert.ok(unsigned.instructions[0]!.keys.some((key) => key.pubkey.toBase58() === AUTHORITY)) + }) + + it('rejects ALTs over 256 addresses', async () => { + const additionalAddresses = Array.from({ length: 247 }, () => + Keypair.generate().publicKey.toBase58(), + ) + + await assert.rejects( + () => generate({ additionalAddresses }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'createLookupTable' && + err.context.param === 'additionalAddresses', + ) + }) + }) + + describe('validation', () => { + it('rejects an ambiguous pool reference before the slot RPC', async () => { + let getSlotCalls = 0 + + await assert.rejects( + new CreateLookupTable().generate( + stubChain(() => getSlotCalls++), + { + tokenAddress: TOKEN, + poolType: 'burn-mint', + poolProgramAddress: POOL_PROGRAM, + payer: PAYER, + } as never, + ), + CCTParamsInvalidError, + ) + + assert.equal(getSlotCalls, 0) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the lookup table address', async () => { + const result = await new CreateLookupTable().execute(stubChain(), { + tokenAddress: TOKEN, + poolProgramAddress: POOL_PROGRAM, + wallet: WALLET, + }) + + assert.equal(result.hash, HASH) + assert.match(result.lookupTableAddress, /^[1-9A-HJ-NP-Za-km-z]+$/) + }) + + it('rejects signed create+extend when authority is not the wallet', async () => { + await assert.rejects( + () => + new CreateLookupTable().execute(stubChain(), { + tokenAddress: TOKEN, + poolProgramAddress: POOL_PROGRAM, + wallet: WALLET, + authority: PAYER, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'createLookupTable' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/create-lookup-table.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/create-lookup-table.ts new file mode 100644 index 000000000..4f04a6842 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/create-lookup-table.ts @@ -0,0 +1,195 @@ +import { + type PublicKey, + type TransactionInstruction, + AddressLookupTableProgram, +} from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + buildCreateLookupTableInstruction, + deriveCcipLookupTableAddresses, +} from '../../programs/alt.ts' +import type { PoolProgramRef } from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + parsePublicKey, + resolvePoolProgram, + validateAuthorityMatchesWallet, +} from '../../validate.ts' + +const MAX_ALT_ADDRESSES = 256 +const EXTEND_CHUNK_SIZE = 30 + +type CreateLookupTableMode = 'createAndExtend' | 'createEmpty' + +/** Parameters shared by Solana TokenAdminRegistry `createLookupTable` generation and execution. */ +type CreateLookupTableParams = + | (PoolProgramRef & { + /** Defaults to `createAndExtend`; use `createEmpty` to skip extending the ALT. */ + mode?: Extract + tokenAddress: string + additionalAddresses?: string[] + /** ALT authority. Defaults to payer for unsigned generation and wallet public key for execute. */ + authority?: string + }) + | { + /** Creates an empty ALT without extend instructions. */ + mode: Extract + /** ALT authority. Defaults to payer for unsigned generation and wallet public key for execute. */ + authority?: string + } + +/** Parameters for unsigned Solana lookup table generation. */ +export type GenerateCreateLookupTableParams = SolanaGenerateParams + +type ParsedCreateLookupTableParams = + | { mode: 'createEmpty'; payer: PublicKey; authority: PublicKey } + | { + mode: 'createAndExtend' + payer: PublicKey + authority: PublicKey + tokenMint: PublicKey + poolProgram: PublicKey + additionalAddresses: PublicKey[] + } + +/** Unsigned create lookup table result, including the derived ALT address. */ +export type GenerateCreateLookupTableResult = UnsignedSolanaTx & { + lookupTableAddress: string +} + +/** Parameters for executing Solana TokenAdminRegistry `createLookupTable`. */ +export type ExecuteCreateLookupTableParams = SolanaExecuteParams + +/** Result of executing Solana TokenAdminRegistry `createLookupTable`. */ +export type ExecuteCreateLookupTableResult = TransactionResult & { lookupTableAddress: string } + +/** Builds and submits Solana ALT create instructions, optionally with extend instructions. */ +export class CreateLookupTable extends SolanaOperation< + CreateLookupTableParams, + GenerateCreateLookupTableResult, + ParsedCreateLookupTableParams +> { + readonly name = 'createLookupTable' + + /** Parses params before `buildUnsigned()` performs any RPC. */ + protected override parse(params: GenerateCreateLookupTableParams): ParsedCreateLookupTableParams { + const payer = parsePublicKey(this.name, 'payer', params.payer) + const authority = + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority) + if (params.mode === 'createEmpty') return { mode: 'createEmpty', payer, authority } + + return { + mode: 'createAndExtend', + payer, + authority, + tokenMint: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + poolProgram: resolvePoolProgram(this.name, params), + additionalAddresses: (params.additionalAddresses ?? []).map((address, i) => + parsePublicKey(this.name, `additionalAddresses[${i}]`, address), + ), + } + } + + /** Builds unsigned ALT create instructions, optionally with extend instructions. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedCreateLookupTableParams, + ): Promise { + const { payer, authority } = opts + + if (opts.mode === 'createEmpty') { + const { instruction: createIx, lookupTableAddress } = buildCreateLookupTableInstruction({ + authority, + payer, + recentSlot: await chain.connection.getSlot('finalized'), + }) + chain.logger.debug( + `${this.name}: mode = createEmpty, lookupTable = ${lookupTableAddress.toBase58()}`, + ) + return { + family: ChainFamily.Solana, + instructions: [createIx], + mainIndex: 0, + lookupTableAddress: lookupTableAddress.toBase58(), + } + } + + const { poolProgram, tokenMint, additionalAddresses } = opts + + const { instruction: createIx, lookupTableAddress } = buildCreateLookupTableInstruction({ + authority, + payer, + recentSlot: await chain.connection.getSlot('finalized'), + }) + + const ccipAddresses = await deriveCcipLookupTableAddresses(chain, { + lookupTableAddress, + tokenMint, + poolProgram, + }) + const addresses = [...ccipAddresses, ...additionalAddresses] + + if (addresses.length > MAX_ALT_ADDRESSES) { + throw new CCTParamsInvalidError( + this.name, + 'additionalAddresses', + `ALT cannot exceed ${MAX_ALT_ADDRESSES} addresses; requested ${addresses.length}`, + ) + } + + const extendIxs: TransactionInstruction[] = [] + for (let i = 0; i < addresses.length; i += EXTEND_CHUNK_SIZE) { + extendIxs.push( + AddressLookupTableProgram.extendLookupTable({ + payer, + authority, + lookupTable: lookupTableAddress, + addresses: addresses.slice(i, i + EXTEND_CHUNK_SIZE), + }), + ) + } + + chain.logger.debug( + `${this.name}: token = ${tokenMint.toBase58()}, lookupTable = ${lookupTableAddress.toBase58()}`, + ) + return { + family: ChainFamily.Solana, + instructions: [createIx, ...extendIxs], + mainIndex: 0, + lookupTableAddress: lookupTableAddress.toBase58(), + } + } + + /** Generate, sign, simulate, send, and confirm with wallet.publicKey as payer. */ + override async execute( + chain: SolanaChain, + params: ExecuteCreateLookupTableParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.mode !== 'createEmpty' && params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + "createAndExtend requires authority to be the executing wallet. Use 'createEmpty' mode for vault-owned ALTs.", + ) + } + + const tx = await this.buildUnsigned(chain, parsed) + const hash = await submit(chain, wallet, tx, this.name, computeUnits) + return { ...hash, lookupTableAddress: tx.lookupTableAddress } + } +} diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/get-supported-tokens.test.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/get-supported-tokens.test.ts new file mode 100644 index 000000000..6501ca9c5 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/get-supported-tokens.test.ts @@ -0,0 +1,45 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair } from '@solana/web3.js' + +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { GetSupportedTokens } from './get-supported-tokens.ts' + +const OFF_RAMP = Keypair.generate().publicKey.toBase58() +const ROUTER = Keypair.generate().publicKey.toBase58() +const TOKENS = [Keypair.generate().publicKey.toBase58()] + +describe('GetSupportedTokens (cct/solana)', () => { + describe('query', () => { + it('resolves an OffRamp to the Router and lists configured token mints', async () => { + let resolvedAddress: string | undefined + let supportedTokensRouter: string | undefined + const chain = { + getTokenAdminRegistryFor: async (address: string) => { + resolvedAddress = address + return ROUTER + }, + getSupportedTokens: async (router: string) => { + supportedTokensRouter = router + return TOKENS + }, + } as unknown as SolanaChain + + assert.deepEqual(await new GetSupportedTokens().query(chain, { address: OFF_RAMP }), TOKENS) + assert.equal(resolvedAddress, OFF_RAMP) + assert.equal(supportedTokensRouter, ROUTER) + }) + }) + + describe('validation', () => { + it('rejects an invalid address', async () => { + await assert.rejects( + () => new GetSupportedTokens().query({} as SolanaChain, { address: 'invalid' }), + (error: unknown) => + error instanceof CCTParamsInvalidError && error.context.param === 'address', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/get-supported-tokens.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/get-supported-tokens.ts new file mode 100644 index 000000000..4ae3f6634 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/get-supported-tokens.ts @@ -0,0 +1,29 @@ +import type { SolanaChain } from '../../../../solana/index.ts' +import { SolanaQuery } from '../../query.ts' +import { validatePublicKey } from '../../validate.ts' + +/** Parameters for listing tokens configured in a Solana TokenAdminRegistry. */ +export type GetSupportedTokensParams = { + /** + * CCIP contract to resolve the TokenAdminRegistry/Router from — a Router or OffRamp + * address works. + */ + address: string +} + +/** Lists all SPL token mints configured in a TokenAdminRegistry in a single scan; pagination is not supported. */ +export class GetSupportedTokens extends SolanaQuery { + readonly name = 'getSupportedTokens' + + /** Validates the resolution address; nothing to convert for {@link read}. */ + protected prepare(params: GetSupportedTokensParams): GetSupportedTokensParams { + validatePublicKey(this.name, 'address', params.address) + return params + } + + /** Resolves the Router and lists its configured token mints. */ + protected async read(chain: SolanaChain, params: GetSupportedTokensParams): Promise { + const router = await chain.getTokenAdminRegistryFor(params.address) + return chain.getSupportedTokens(router) + } +} diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/get-token-admin-registry.test.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/get-token-admin-registry.test.ts new file mode 100644 index 000000000..de85c018b --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/get-token-admin-registry.test.ts @@ -0,0 +1,158 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { BorshAccountsCoder } from '@coral-xyz/anchor' +import { Keypair, PublicKey, SystemProgram } from '@solana/web3.js' + +import { + CCIPDataFormatUnsupportedError, + CCIPTokenNotConfiguredError, +} from '../../../../errors/index.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveTokenAdminRegistryPda } from '../../programs/router.ts' +import { GetTokenAdminRegistry } from './get-token-admin-registry.ts' + +const ROUTER = Keypair.generate().publicKey +const TOKEN = Keypair.generate().publicKey +const ADMINISTRATOR = Keypair.generate().publicKey +const PENDING_ADMINISTRATOR = Keypair.generate().publicKey +const LOOKUP_TABLE = Keypair.generate().publicKey +const POOL = Keypair.generate().publicKey +const REGISTRY = deriveTokenAdminRegistryPda(ROUTER, TOKEN) + +function registryAccount( + pendingAdministrator = PENDING_ADMINISTRATOR, + poolLookupTable = LOOKUP_TABLE, + supportsAutoDerivation = true, + hasSupportsAutoDerivation = true, +) { + const data = Buffer.alloc(hasSupportsAutoDerivation ? 170 : 169) + BorshAccountsCoder.accountDiscriminator('TokenAdminRegistry').copy(data) + data[8] = 2 + ADMINISTRATOR.toBuffer().copy(data, 9) + pendingAdministrator.toBuffer().copy(data, 41) + poolLookupTable.toBuffer().copy(data, 73) + data[120] = 0x19 // Writable indexes 3, 4, and 7 use the high bits of the first u128 bitmap. + data[136] = 0x20 // Writable index 130 uses the high bits of the second u128 bitmap. + TOKEN.toBuffer().copy(data, 137) + if (hasSupportsAutoDerivation && supportsAutoDerivation) data[169] = 1 + return { data } +} + +function stubChain(account: { data: Buffer } | null = registryAccount()): SolanaChain { + return { + connection: { + getAccountInfo: async (address: PublicKey) => (address.equals(REGISTRY) ? account : null), + getAddressLookupTable: async (address: PublicKey) => ({ + value: address.equals(LOOKUP_TABLE) + ? { + state: { addresses: [PublicKey.default, PublicKey.default, PublicKey.default, POOL] }, + } + : null, + }), + }, + getTokenAdminRegistryFor: async () => ROUTER.toBase58(), + } as unknown as SolanaChain +} + +describe('GetTokenAdminRegistry (cct/solana)', () => { + describe('query', () => { + it('returns configured administrators, lookup table, and writable indexes', async () => { + const config = await new GetTokenAdminRegistry().query(stubChain(), { + address: ROUTER.toBase58(), + tokenAddress: TOKEN.toBase58(), + }) + + assert.deepEqual(config, { + mint: TOKEN.toBase58(), + administrator: ADMINISTRATOR.toBase58(), + pendingAdministrator: PENDING_ADMINISTRATOR.toBase58(), + tokenPool: POOL.toBase58(), + lookupTable: LOOKUP_TABLE.toBase58(), + writableIndexes: [3, 4, 7, 130], + supportsAutoDerivation: true, + }) + }) + + it('omits optional fields when unset', async () => { + const config = await new GetTokenAdminRegistry().query( + stubChain(registryAccount(PublicKey.default, PublicKey.default, false, false)), + { address: ROUTER.toBase58(), tokenAddress: TOKEN.toBase58() }, + ) + + assert.deepEqual(config, { + mint: TOKEN.toBase58(), + administrator: ADMINISTRATOR.toBase58(), + writableIndexes: [3, 4, 7, 130], + supportsAutoDerivation: false, + }) + }) + + it('returns disabled auto derivation setting', async () => { + const config = await new GetTokenAdminRegistry().query( + stubChain(registryAccount(PENDING_ADMINISTRATOR, LOOKUP_TABLE, false)), + { address: ROUTER.toBase58(), tokenAddress: TOKEN.toBase58() }, + ) + + assert.equal(config.supportsAutoDerivation, false) + }) + + it('omits the system program as pending administrator', async () => { + const config = await new GetTokenAdminRegistry().query( + stubChain(registryAccount(SystemProgram.programId)), + { address: ROUTER.toBase58(), tokenAddress: TOKEN.toBase58() }, + ) + + assert.equal(config.pendingAdministrator, undefined) + }) + + it('rejects malformed registry data', async () => { + await assert.rejects( + () => + new GetTokenAdminRegistry().query(stubChain({ data: Buffer.alloc(8) }), { + address: ROUTER.toBase58(), + tokenAddress: TOKEN.toBase58(), + }), + CCIPDataFormatUnsupportedError, + ) + }) + + it('rejects unregistered tokens', async () => { + await assert.rejects( + () => + new GetTokenAdminRegistry().query(stubChain(null), { + address: ROUTER.toBase58(), + tokenAddress: TOKEN.toBase58(), + }), + CCIPTokenNotConfiguredError, + ) + }) + }) + + describe('validation', () => { + it('rejects an invalid router address', async () => { + await assert.rejects( + () => + new GetTokenAdminRegistry().query(stubChain(), { + address: 'invalid', + tokenAddress: TOKEN.toBase58(), + }), + (error: unknown) => + error instanceof CCTParamsInvalidError && error.context.param === 'address', + ) + }) + + it('rejects an invalid token address', async () => { + await assert.rejects( + () => + new GetTokenAdminRegistry().query(stubChain(), { + address: ROUTER.toBase58(), + tokenAddress: 'invalid', + }), + (error: unknown) => + error instanceof CCTParamsInvalidError && error.context.param === 'tokenAddress', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/get-token-admin-registry.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/get-token-admin-registry.ts new file mode 100644 index 000000000..463ddbbf9 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/get-token-admin-registry.ts @@ -0,0 +1,67 @@ +import { PublicKey } from '@solana/web3.js' + +import type { RegistryTokenConfig } from '../../../../chain.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { getTokenAdminRegistryConfig } from '../../../../solana/token-admin-registry.ts' +import { SolanaQuery } from '../../query.ts' +import { parsePublicKey, validatePublicKey } from '../../validate.ts' + +/** Parameters for reading a Solana TokenAdminRegistry configuration. */ +export type GetTokenAdminRegistryParams = { + /** + * CCIP contract to resolve the TokenAdminRegistry/Router from — a Router or OffRamp + * address works. + */ + address: string + /** SPL token mint registered with the Router. */ + tokenAddress: string +} + +/** Configuration stored in a Solana TokenAdminRegistry account. */ +export type GetTokenAdminRegistryResult = RegistryTokenConfig & { + mint: string + lookupTable?: string + writableIndexes: number[] + supportsAutoDerivation: boolean +} + +/** {@link GetTokenAdminRegistryParams} with its mint resolved to a public key. */ +type ParsedGetTokenAdminRegistryParams = GetTokenAdminRegistryParams & { + tokenMint: PublicKey +} + +/** Reads a token's TokenAdminRegistry account. */ +export class GetTokenAdminRegistry extends SolanaQuery< + GetTokenAdminRegistryParams, + GetTokenAdminRegistryResult, + ParsedGetTokenAdminRegistryParams +> { + readonly name = 'getTokenAdminRegistry' + + /** Converts the mint; `address` stays a string for the Router lookup in {@link read}. */ + protected prepare(params: GetTokenAdminRegistryParams): ParsedGetTokenAdminRegistryParams { + validatePublicKey(this.name, 'address', params.address) + return { ...params, tokenMint: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress) } + } + + /** Reads and serializes the TokenAdminRegistry account. */ + protected async read( + chain: SolanaChain, + params: ParsedGetTokenAdminRegistryParams, + ): Promise { + const router = new PublicKey(await chain.getTokenAdminRegistryFor(params.address)) + const config = await getTokenAdminRegistryConfig(chain.connection, router, params.tokenMint) + + return { + mint: config.mint.toBase58(), + administrator: config.administrator.toBase58(), + ...(config.pendingAdministrator && { + pendingAdministrator: config.pendingAdministrator.toBase58(), + }), + ...(config.tokenPool && { tokenPool: config.tokenPool.toBase58() }), + ...(config.lookupTable && { lookupTable: config.lookupTable.toBase58() }), + writableIndexes: config.writableIndexes, + supportsAutoDerivation: config.supportsAutoDerivation, + } + } +} diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/index.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/index.ts new file mode 100644 index 000000000..8a5fcd7cf --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/index.ts @@ -0,0 +1,9 @@ +export * from './accept-admin.ts' +export * from './append-to-lookup-table.ts' +export * from './create-lookup-table.ts' +export * from './get-supported-tokens.ts' +export * from './get-token-admin-registry.ts' +export * from './owner-override-pending-administrator.ts' +export * from './register-admin.ts' +export * from './set-pool.ts' +export * from './transfer-admin.ts' diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/owner-override-pending-administrator.test.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/owner-override-pending-administrator.test.ts new file mode 100644 index 000000000..13a910b7e --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/owner-override-pending-administrator.test.ts @@ -0,0 +1,169 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveRouterConfigPda, deriveTokenAdminRegistryPda } from '../../programs/router.ts' +import { + type GenerateOwnerOverridePendingAdministratorParams, + OwnerOverridePendingAdministrator, +} from './owner-override-pending-administrator.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const ADDRESS = Keypair.generate().publicKey.toBase58() +const ROUTER = Keypair.generate().publicKey.toBase58() +const OWNER = Keypair.generate().publicKey.toBase58() +const NEW_ADMIN = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: new PublicKey(OWNER), + signTransaction: async (tx: T) => tx, +} + +function stubChain( + administrator = PublicKey.default.toBase58(), + onAddress?: (address: string) => void, +): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + getTokenAdminRegistryFor: async (address: string) => { + onAddress?.(address) + return ROUTER + }, + getRegistryTokenConfig: async () => ({ administrator }), + } as unknown as SolanaChain +} + +function generate(opts: Partial = {}) { + return new OwnerOverridePendingAdministrator().generate(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + newAdmin: NEW_ADMIN, + payer: OWNER, + authority: OWNER, + ...opts, + }) +} + +describe('OwnerOverridePendingAdministrator (cct/solana)', () => { + describe('generate', () => { + it('builds an unsigned owner override pending administrator instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + + assert.ok(instruction) + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction.programId.toBase58(), ROUTER) + assert.equal(instruction.data.subarray(0, 8).toString('hex'), 'e66f8695cba876c9') + assert.deepEqual(instruction.data.subarray(8), new PublicKey(NEW_ADMIN).toBuffer()) + assert.deepEqual( + instruction.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveRouterConfigPda(new PublicKey(ROUTER)).toBase58(), + isSigner: false, + isWritable: false, + }, + { + pubkey: deriveTokenAdminRegistryPda( + new PublicKey(ROUTER), + new PublicKey(TOKEN), + ).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: TOKEN, isSigner: false, isWritable: false }, + { pubkey: OWNER, isSigner: true, isWritable: true }, + { pubkey: PublicKey.default.toBase58(), isSigner: false, isWritable: false }, + ], + ) + }) + + it('resolves the router from address', async () => { + let requestedAddress: string | undefined + await new OwnerOverridePendingAdministrator().generate( + stubChain(PublicKey.default.toBase58(), (address) => (requestedAddress = address)), + { tokenAddress: TOKEN, address: ADDRESS, newAdmin: NEW_ADMIN, payer: OWNER }, + ) + + assert.equal(requestedAddress, ADDRESS) + }) + }) + + describe('validation', () => { + for (const param of ['tokenAddress', 'address', 'newAdmin', 'authority'] as const) { + it(`rejects an invalid ${param}`, async () => { + await assert.rejects( + () => generate({ [param]: 'not-a-public-key' }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + }) + } + + it('rejects an accepted registry administrator before building the transaction', async () => { + await assert.rejects( + () => + new OwnerOverridePendingAdministrator().generate(stubChain(OWNER), { + tokenAddress: TOKEN, + address: ADDRESS, + newAdmin: NEW_ADMIN, + payer: OWNER, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'tokenAddress' && + typeof err.context.reason === 'string' && + err.context.reason.includes('The current administrator must use transferAdmin instead'), + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + assert.deepEqual( + await new OwnerOverridePendingAdministrator().execute(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + newAdmin: NEW_ADMIN, + wallet: WALLET, + }), + { hash: HASH }, + ) + }) + + it('requires the mint authority to be the executing wallet', async () => { + await assert.rejects( + () => + new OwnerOverridePendingAdministrator().execute(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + newAdmin: NEW_ADMIN, + authority: NEW_ADMIN, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'authority' && + typeof err.context.reason === 'string' && + err.context.reason.includes('requires authority to be the executing wallet'), + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/owner-override-pending-administrator.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/owner-override-pending-administrator.ts new file mode 100644 index 000000000..54f212e3a --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/owner-override-pending-administrator.ts @@ -0,0 +1,136 @@ +import { PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + createRouterProgram, + deriveRouterConfigPda, + deriveTokenAdminRegistryPda, +} from '../../programs/router.ts' +import { submit } from '../../submit.ts' +import { parsePublicKey, validateAuthorityMatchesWallet } from '../../validate.ts' + +/** Parameters shared by owner override pending administrator generation and execution. */ +type OwnerOverridePendingAdministratorParams = { + /** Token mint whose pending registry administrator is being replaced. */ + tokenAddress: string + /** CCIP contract to resolve the TokenAdminRegistry/Router from — a Router or OffRamp works. */ + address: string + /** Administrator to propose as the replacement pending administrator. */ + newAdmin: string + /** Mint authority. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +/** Parameters for unsigned Solana owner override pending administrator generation. */ +export type GenerateOwnerOverridePendingAdministratorParams = + SolanaGenerateParams + +/** Unsigned Solana owner override pending administrator result. */ +export type GenerateOwnerOverridePendingAdministratorResult = UnsignedSolanaTx + +/** Parameters for executing Solana owner override pending administrator. */ +export type ExecuteOwnerOverridePendingAdministratorParams = + SolanaExecuteParams + +/** Result of executing Solana owner override pending administrator. */ +export type ExecuteOwnerOverridePendingAdministratorResult = TransactionResult + +type ParsedOwnerOverridePendingAdministratorParams = { + tokenMint: PublicKey + address: PublicKey + newAdmin: PublicKey + payer: PublicKey + authority: PublicKey +} + +/** Replaces an initial pending TokenAdminRegistry administrator using the mint authority. */ +export class OwnerOverridePendingAdministrator extends SolanaOperation< + OwnerOverridePendingAdministratorParams, + UnsignedSolanaTx, + ParsedOwnerOverridePendingAdministratorParams +> { + readonly name = 'ownerOverridePendingAdministrator' + + /** Parses public keys and defaults authority to payer without mutating caller params. */ + protected override parse( + params: GenerateOwnerOverridePendingAdministratorParams, + ): ParsedOwnerOverridePendingAdministratorParams { + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenMint: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + address: parsePublicKey(this.name, 'address', params.address), + newAdmin: parsePublicKey(this.name, 'newAdmin', params.newAdmin), + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + } + } + + /** Builds the override instruction. The Router verifies the mint authority and initial state on-chain. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedOwnerOverridePendingAdministratorParams, + ): Promise { + const { tokenMint, payer, authority, newAdmin } = opts + const router = new PublicKey(await chain.getTokenAdminRegistryFor(opts.address.toBase58())) + const tokenConfig = await chain.getRegistryTokenConfig(router.toBase58(), tokenMint.toBase58()) + if (!new PublicKey(tokenConfig.administrator).equals(PublicKey.default)) { + throw new CCTParamsInvalidError( + this.name, + 'tokenAddress', + `cannot override the pending administrator because ${tokenConfig.administrator} has already accepted the role; only initial registrations can be overridden. The current administrator must use transferAdmin instead.`, + ) + } + + const instruction = await createRouterProgram(chain, router, payer) + .methods.ownerOverridePendingAdministrator(newAdmin) + .accounts({ + config: deriveRouterConfigPda(router), + tokenAdminRegistry: deriveTokenAdminRegistryPda(router, tokenMint), + mint: tokenMint, + authority, + }) + .instruction() + + chain.logger.debug( + `${ + this.name + }: router = ${router.toBase58()}, token = ${tokenMint.toBase58()}, newAdmin = ${newAdmin.toBase58()}`, + ) + return { + family: ChainFamily.Solana, + instructions: [instruction], + mainIndex: 0, + } + } + + /** Generate, sign, simulate, send, and confirm with the mint authority wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteOwnerOverridePendingAdministratorParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'ownerOverridePendingAdministrator requires authority to be the executing wallet. Use generateUnsignedOwnerOverridePendingAdministrator for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/register-admin.test.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/register-admin.test.ts new file mode 100644 index 000000000..053000883 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/register-admin.test.ts @@ -0,0 +1,227 @@ +import assert from 'node:assert/strict' +import { createHash } from 'node:crypto' +import { describe, it } from 'node:test' + +import { TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveRouterConfigPda, deriveTokenAdminRegistryPda } from '../../programs/router.ts' +import { RegisterAdmin } from './register-admin.ts' + +const TOKEN = Keypair.generate().publicKey +const MINT_AUTHORITY = Keypair.generate().publicKey +const ADDRESS = Keypair.generate().publicKey.toBase58() +const ROUTER = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const CCIP_ADMIN = Keypair.generate().publicKey +const ADMINISTRATOR = Keypair.generate().publicKey +const CONFIG = deriveRouterConfigPda(new PublicKey(ROUTER)) +const TOKEN_ADMIN_REGISTRY = deriveTokenAdminRegistryPda(new PublicKey(ROUTER), TOKEN) +const HASH = Keypair.generate().publicKey.toBase58() +const SUBMIT_WALLET = { + publicKey: MINT_AUTHORITY, + signTransaction: async (tx: T) => tx, +} +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function configAccount() { + const data = Buffer.alloc(210) + createHash('sha256').update('account:Config').digest().copy(data, 0, 0, 8) + data[8] = 1 + CCIP_ADMIN.toBuffer().copy(data, 18) + return { data, executable: false, lamports: 1, owner: new PublicKey(ROUTER), rentEpoch: 0 } +} + +function mintAccount(mintAuthority: PublicKey | null = MINT_AUTHORITY) { + const data = Buffer.alloc(82) + if (mintAuthority) { + data.writeUInt32LE(1, 0) + mintAuthority.toBuffer().copy(data, 4) + } + data[44] = 9 + data[45] = 1 + return { data, executable: false, lamports: 1, owner: TOKEN_PROGRAM_ID, rentEpoch: 0 } +} + +function stubChain( + registered = false, + mintAuthority: PublicKey | null = MINT_AUTHORITY, + configAvailable = true, +): SolanaChain { + const getAccountInfo = async (address: PublicKey) => { + if (address.equals(TOKEN)) return mintAccount(mintAuthority) + if (address.equals(TOKEN_ADMIN_REGISTRY)) return registered ? mintAccount() : null + if (address.equals(CONFIG)) return configAvailable ? configAccount() : null + return assert.fail('unexpected account lookup') + } + + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + getAccountInfo, + getAccountInfoAndContext: async (address: PublicKey) => ({ + context: { slot: 0 }, + value: await getAccountInfo(address), + }), + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + getTokenAdminRegistryFor: async () => ROUTER, + } as unknown as SolanaChain +} + +function generate(opts = {}, registered = false, mintAuthority: PublicKey | null = MINT_AUTHORITY) { + return new RegisterAdmin().generate(stubChain(registered, mintAuthority), { + tokenAddress: TOKEN.toBase58(), + address: ADDRESS, + payer: PAYER, + authority: MINT_AUTHORITY.toBase58(), + ...opts, + }) +} + +describe('RegisterAdmin (cct/solana)', () => { + describe('generate', () => { + it('builds owner registration with the mint authority as proposed admin', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + + assert.ok(instruction) + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction.programId.toBase58(), ROUTER) + assert.equal(instruction.data.subarray(0, 8).toString('hex'), 'af51a0f6ce841216') + assert.ok(instruction.keys.some((key) => key.pubkey.equals(MINT_AUTHORITY))) + assert.deepEqual(instruction.data.subarray(-32), MINT_AUTHORITY.toBuffer()) + }) + + it('builds the CCIP-admin registration instruction without a mint authority', async () => { + const ccipAdmin = await generate( + { + registrationMethod: 'ccip-admin', + authority: CCIP_ADMIN.toBase58(), + administrator: ADMINISTRATOR.toBase58(), + }, + false, + null, + ) + + assert.equal( + ccipAdmin.instructions[0]!.data.subarray(0, 8).toString('hex'), + 'da258b6b8ee433db', + ) + assert.deepEqual(ccipAdmin.instructions[0]!.data.subarray(-32), ADMINISTRATOR.toBuffer()) + }) + }) + + describe('validation', () => { + it('rejects owner registration when authority is not the mint authority', async () => { + await assert.rejects( + () => generate({ authority: PAYER }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'authority', + ) + }) + + it('rejects a token that is already registered', async () => { + await assert.rejects( + () => generate({}, true), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'tokenAddress', + ) + }) + + it('rejects owner registration without a mint authority', async () => { + await assert.rejects( + () => + generate( + { registrationMethod: 'owner', administrator: ADMINISTRATOR.toBase58() }, + false, + null, + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'tokenAddress', + ) + }) + + it('requires an administrator for CCIP-admin registration without a mint authority', async () => { + await assert.rejects( + () => + generate( + { registrationMethod: 'ccip-admin', authority: CCIP_ADMIN.toBase58() }, + false, + null, + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'administrator', + ) + }) + + it('rejects a missing Router config with a typed error', async () => { + await assert.rejects( + () => + new RegisterAdmin().generate(stubChain(false, MINT_AUTHORITY, false), { + tokenAddress: TOKEN.toBase58(), + address: ADDRESS, + registrationMethod: 'ccip-admin', + payer: PAYER, + authority: CCIP_ADMIN.toBase58(), + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'address', + ) + }) + + it('rejects CCIP-admin registration when authority is not the Router CCIP admin', async () => { + await assert.rejects( + () => generate({ registrationMethod: 'ccip-admin' }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'authority', + ) + }) + + it('rejects an unknown registration method before RPC', async () => { + await assert.rejects( + () => generate({ registrationMethod: 'other' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'registrationMethod', + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + assert.deepEqual( + await new RegisterAdmin().execute(stubChain(), { + tokenAddress: TOKEN.toBase58(), + address: ADDRESS, + registrationMethod: 'owner', + wallet: SUBMIT_WALLET, + }), + { hash: HASH }, + ) + }) + + it('rejects an authority that differs from the executing wallet', async () => { + await assert.rejects( + () => + new RegisterAdmin().execute(stubChain(), { + tokenAddress: TOKEN.toBase58(), + address: ADDRESS, + registrationMethod: 'owner', + authority: MINT_AUTHORITY.toBase58(), + wallet: WALLET, + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/register-admin.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/register-admin.ts new file mode 100644 index 000000000..8e85a8a55 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/register-admin.ts @@ -0,0 +1,245 @@ +import { unpackMint } from '@solana/spl-token' +import { type TransactionInstruction, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { resolveTokenMint } from '../../../../solana/utils.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + createRouterProgram, + deriveRouterConfigPda, + deriveTokenAdminRegistryPda, +} from '../../programs/router.ts' +import { submit } from '../../submit.ts' +import { parsePublicKey, validateAuthorityMatchesWallet } from '../../validate.ts' +import { REGISTRATION_METHODS } from '../constants.ts' + +/** Authorization path used to register a token in the TokenAdminRegistry. */ +export type RegisterAdminMethod = (typeof REGISTRATION_METHODS)[keyof typeof REGISTRATION_METHODS] + +type RegisterAdminParams = { + /** Token mint to register. The proposed administrator remains pending until accepted. */ + tokenAddress: string + /** + * CCIP contract to resolve the TokenAdminRegistry/Router from — a Router or OffRamp + * address works. + */ + address: string + /** Selects registration authority; defaults to `owner`. */ + registrationMethod?: RegisterAdminMethod + /** Registry administrator to propose. Defaults to the mint authority when present. */ + administrator?: string + /** + * Registration authority. Defaults to `payer` for single-signer transactions. + * Multisig/Squads flows should pass the mint or CCIP admin/vault authority explicitly. + */ + authority?: string +} + +/** Parameters for unsigned Solana token registration generation. */ +export type GenerateRegisterAdminParams = SolanaGenerateParams + +type ParsedRegisterAdminParams = { + tokenMint: PublicKey + address: PublicKey + payer: PublicKey + authority: PublicKey + administrator?: PublicKey + method: RegisterAdminMethod +} + +/** Unsigned Solana token registration result. */ +export type GenerateRegisterAdminResult = UnsignedSolanaTx + +/** Parameters for executing Solana token registration. */ +export type ExecuteRegisterAdminParams = SolanaExecuteParams + +/** Result of executing Solana token registration. */ +export type ExecuteRegisterAdminResult = TransactionResult + +type RegisterAdminAccounts = { + config: PublicKey + tokenAdminRegistry: PublicKey + mint: PublicKey + authority: PublicKey +} + +type RouterProgram = ReturnType + +async function buildOwnerInstruction( + program: RouterProgram, + accounts: RegisterAdminAccounts, + mintAuthority: PublicKey | null, + administrator: PublicKey, +): Promise { + if (!mintAuthority) { + throw new CCTParamsInvalidError( + 'registerAdmin', + 'tokenAddress', + 'token mint has no mint authority; use ccip-admin with administrator', + ) + } + if (!accounts.authority.equals(mintAuthority)) { + throw new CCTParamsInvalidError( + 'registerAdmin', + 'authority', + 'must match the token mint authority', + ) + } + return program.methods.ownerProposeAdministrator(administrator).accounts(accounts).instruction() +} + +async function buildCcipAdminInstruction( + program: RouterProgram, + accounts: RegisterAdminAccounts, + administrator: PublicKey, +): Promise { + let routerConfig + try { + routerConfig = await program.account.config.fetch(accounts.config) + } catch (cause) { + throw new CCTParamsInvalidError( + 'registerAdmin', + 'address', + 'Router config could not be fetched', + { + cause: cause instanceof Error ? cause : undefined, + }, + ) + } + + if (!accounts.authority.equals(routerConfig.owner)) { + throw new CCTParamsInvalidError( + 'registerAdmin', + 'authority', + 'must match the Router CCIP admin', + ) + } + return program.methods + .ccipAdminProposeAdministrator(administrator) + .accounts(accounts) + .instruction() +} + +/** Registers a token through either its mint authority or the Router CCIP admin. */ +export class RegisterAdmin extends SolanaOperation< + RegisterAdminParams, + UnsignedSolanaTx, + ParsedRegisterAdminParams +> { + readonly name = 'registerAdmin' + + /** Parses all caller-supplied parameters before RPC. */ + protected override parse(params: GenerateRegisterAdminParams): ParsedRegisterAdminParams { + if ( + params.registrationMethod !== undefined && + !Object.values(REGISTRATION_METHODS).includes(params.registrationMethod) + ) { + throw new CCTParamsInvalidError( + this.name, + 'registrationMethod', + 'must be owner or ccip-admin', + ) + } + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenMint: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + address: parsePublicKey(this.name, 'address', params.address), + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + ...(params.administrator !== undefined && { + administrator: parsePublicKey(this.name, 'administrator', params.administrator), + }), + method: params.registrationMethod ?? REGISTRATION_METHODS.OWNER, + } + } + + /** Builds an unsigned token registration instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedRegisterAdminParams, + ): Promise { + const router = new PublicKey(await chain.getTokenAdminRegistryFor(opts.address.toBase58())) + const { tokenMint, payer, authority, method } = opts + + const mintAccount = await resolveTokenMint(chain.connection, tokenMint) + const { mintAuthority } = unpackMint(tokenMint, mintAccount, mintAccount.owner) + const administrator = opts.administrator ?? mintAuthority + const config = deriveRouterConfigPda(router) + const tokenAdminRegistry = deriveTokenAdminRegistryPda(router, tokenMint) + if (await chain.connection.getAccountInfo(tokenAdminRegistry)) { + throw new CCTParamsInvalidError( + this.name, + 'tokenAddress', + 'a registry entry already exists for this token (possibly pending admin acceptance) — use acceptAdmin/setPool instead of registering again', + ) + } + + const program = createRouterProgram(chain, router, payer) + const accounts = { config, tokenAdminRegistry, mint: tokenMint, authority } + + if (!administrator) { + throw new CCTParamsInvalidError( + this.name, + 'administrator', + 'is required when the mint has no mint authority', + ) + } + + const instructions: TransactionInstruction[] = [] + switch (method) { + case REGISTRATION_METHODS.OWNER: { + const ownerIx = await buildOwnerInstruction(program, accounts, mintAuthority, administrator) + instructions.push(ownerIx) + break + } + case REGISTRATION_METHODS.CCIP_ADMIN: { + const ccipAdminIx = await buildCcipAdminInstruction(program, accounts, administrator) + instructions.push(ccipAdminIx) + break + } + default: + throw new CCTParamsInvalidError( + this.name, + 'registrationMethod', + 'must be owner or ccip-admin', + ) + } + + chain.logger.debug( + `${this.name}: method = ${method}, router = ${router.toBase58()}, token = ${tokenMint.toBase58()}`, + ) + + return { family: ChainFamily.Solana, instructions, mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the registration authority. */ + override async execute( + chain: SolanaChain, + params: ExecuteRegisterAdminParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'registerAdmin requires authority to be the executing wallet. Use generateUnsignedRegisterAdmin for externally signed transactions.', + ) + } + + const tx = await this.buildUnsigned(chain, parsed) + return submit(chain, wallet, tx, this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/set-pool.test.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/set-pool.test.ts new file mode 100644 index 000000000..f9cfd4cd5 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/set-pool.test.ts @@ -0,0 +1,135 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey } from '@solana/web3.js' + +import { CCIPWalletInvalidError } from '../../../../errors/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { DEFAULT_WRITABLE_INDEXES } from '../constants.ts' +import { SetPool } from './set-pool.ts' + +const BLOCKHASH = PublicKey.default.toBase58() +const TOKEN = Keypair.generate().publicKey.toBase58() +const ADDRESS = Keypair.generate().publicKey.toBase58() +const ROUTER = Keypair.generate().publicKey.toBase58() +const POOL_LOOKUP_TABLE = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() + +function stubChain(router = ROUTER, onAddress?: (address: string) => void): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + getAccountInfo: () => assert.fail('should not RPC before validation'), + getLatestBlockhash: async () => ({ blockhash: BLOCKHASH, lastValidBlockHeight: 0 }), + }, + getTokenAdminRegistryFor: async (address: string) => { + onAddress?.(address) + return router + }, + } as unknown as SolanaChain +} + +function generate(opts = {}) { + return new SetPool().generate(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + poolLookupTableAddress: POOL_LOOKUP_TABLE, + payer: PAYER, + ...opts, + }) +} + +describe('SetPool (cct/solana)', () => { + describe('generate', () => { + it('builds unsigned setPool instruction with default writable indexes and authority', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + + assert.ok(instruction) + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(unsigned.instructions.length, 1) + assert.equal(instruction.programId.toBase58(), ROUTER) + assert.equal(instruction.data.toString('hex'), '771e0eb473e1a7ee03000000030407') + assert.ok(instruction.keys.some((key) => key.pubkey.toBase58() === TOKEN)) + assert.ok(instruction.keys.some((key) => key.pubkey.toBase58() === POOL_LOOKUP_TABLE)) + assert.ok(instruction.keys.some((key) => key.pubkey.toBase58() === PAYER)) + }) + + it('accepts the default writable indexes directly', async () => { + const unsigned = await generate({ writableIndexes: DEFAULT_WRITABLE_INDEXES }) + + assert.equal(unsigned.instructions[0]!.data.toString('hex'), '771e0eb473e1a7ee03000000030407') + }) + + it('uses caller-provided writable indexes', async () => { + const unsigned = await generate({ writableIndexes: [3, 4, 7, 9] }) + + assert.equal( + unsigned.instructions[0]!.data.toString('hex'), + '771e0eb473e1a7ee0400000003040709', + ) + }) + + it('resolves the router from address', async () => { + let requestedAddress: string | undefined + const unsigned = await new SetPool().generate( + stubChain(ROUTER, (address) => (requestedAddress = address)), + { + tokenAddress: TOKEN, + address: ADDRESS, + poolLookupTableAddress: POOL_LOOKUP_TABLE, + payer: PAYER, + }, + ) + + assert.equal(requestedAddress, ADDRESS) + assert.equal(unsigned.instructions[0]!.programId.toBase58(), ROUTER) + }) + + it('uses caller-provided authority', async () => { + const unsigned = await generate({ authority: AUTHORITY }) + + assert.ok(unsigned.instructions[0]!.keys.some((key) => key.pubkey.toBase58() === AUTHORITY)) + }) + }) + + describe('validation', () => { + it('rejects invalid writable indexes before resolving the router', async () => { + let routerLookups = 0 + + await assert.rejects( + new SetPool().generate( + stubChain(ROUTER, () => routerLookups++), + { + tokenAddress: TOKEN, + address: ADDRESS, + poolLookupTableAddress: POOL_LOOKUP_TABLE, + payer: PAYER, + writableIndexes: [], + }, + ), + CCTParamsInvalidError, + ) + + assert.equal(routerLookups, 0) + }) + }) + + describe('execute', () => { + it('rejects an invalid wallet before generating instructions', async () => { + await assert.rejects( + new SetPool().execute(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + poolLookupTableAddress: POOL_LOOKUP_TABLE, + wallet: {}, + }), + CCIPWalletInvalidError, + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/set-pool.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/set-pool.ts new file mode 100644 index 000000000..40d649308 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/set-pool.ts @@ -0,0 +1,120 @@ +import { Buffer } from 'buffer' + +import { PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + createRouterProgram, + deriveRouterConfigPda, + deriveTokenAdminRegistryPda, +} from '../../programs/router.ts' +import { parsePublicKey, validateWritableIndexes } from '../../validate.ts' +import { DEFAULT_WRITABLE_INDEXES } from '../constants.ts' + +/** Parameters shared by Solana TokenAdminRegistry `setPool` generation and execution. */ +type SetPoolParams = { + tokenAddress: string + /** + * CCIP contract to resolve the TokenAdminRegistry/Router from — the registry itself, + * a Router, OnRamp, OffRamp, or TokenPool address all work. + */ + address: string + /** The pool's Address Lookup Table address, produced by the `createLookupTable` op. */ + poolLookupTableAddress: string + /** + * Positions in the pool's own Address Lookup Table the Router marks writable during a + * transfer. Defaults to {@link DEFAULT_WRITABLE_INDEXES} for standard BurnMint/LockRelease + * pools; custom pools with extra accounts MUST extend this or the pool CPI gets wrong + * write-permissions and fails at execution. Each entry is a byte (0–255). + */ + writableIndexes?: readonly number[] + /** + * Token admin authority. Defaults to `payer` for single-signer transactions. + * Multisig/Squads flows should pass the admin/vault authority explicitly. + */ + authority?: string +} + +/** Parameters for unsigned Solana TokenAdminRegistry `setPool` generation. */ +export type GenerateSetPoolParams = SolanaGenerateParams + +type ParsedSetPoolParams = { + tokenMint: PublicKey + address: PublicKey + lookupTable: PublicKey + payer: PublicKey + authority: PublicKey + writableIndexes: number[] +} + +/** Unsigned Solana TokenAdminRegistry `setPool` result. */ +export type GenerateSetPoolResult = UnsignedSolanaTx + +/** Parameters for executing Solana TokenAdminRegistry `setPool`. */ +export type ExecuteSetPoolParams = SolanaExecuteParams + +/** Result of executing Solana TokenAdminRegistry `setPool`. */ +export type ExecuteSetPoolResult = TransactionResult + +/** Solana TokenAdminRegistry `setPool` operation. */ +export class SetPool extends SolanaOperation { + readonly name = 'setPool' + + /** Parses all public keys before any RPC. */ + protected override parse(params: GenerateSetPoolParams): ParsedSetPoolParams { + validateWritableIndexes(this.name, 'writableIndexes', params.writableIndexes) + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenMint: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + address: parsePublicKey(this.name, 'address', params.address), + lookupTable: parsePublicKey( + this.name, + 'poolLookupTableAddress', + params.poolLookupTableAddress, + ), + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + writableIndexes: [...(params.writableIndexes ?? DEFAULT_WRITABLE_INDEXES)], + } + } + + /** Builds the unsigned Solana `setPool` instruction set. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedSetPoolParams, + ): Promise { + const router = new PublicKey(await chain.getTokenAdminRegistryFor(opts.address.toBase58())) + const { tokenMint, payer, authority, lookupTable } = opts + + const routerProgram = createRouterProgram(chain, router, payer) + const config = deriveRouterConfigPda(router) + const tokenAdminRegistry = deriveTokenAdminRegistryPda(router, tokenMint) + + const instruction = await routerProgram.methods + .setPool(Buffer.from(opts.writableIndexes)) + .accounts({ + config, + tokenAdminRegistry, + mint: tokenMint, + poolLookuptable: lookupTable, + authority, + }) + .instruction() + + chain.logger.debug( + `${this.name}: router = ${router.toBase58()}, token = ${tokenMint.toBase58()}, lookupTable = ${lookupTable.toBase58()}`, + ) + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } +} diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/transfer-admin.test.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/transfer-admin.test.ts new file mode 100644 index 000000000..ea36b3ca5 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/transfer-admin.test.ts @@ -0,0 +1,179 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveRouterConfigPda, deriveTokenAdminRegistryPda } from '../../programs/router.ts' +import { type GenerateTransferAdminParams, TransferAdmin } from './transfer-admin.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const ADDRESS = Keypair.generate().publicKey.toBase58() +const ROUTER = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const NEW_ADMIN = Keypair.generate().publicKey.toBase58() +const CURRENT_ADMIN = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const SUBMIT_WALLET = { + publicKey: new PublicKey(CURRENT_ADMIN), + signTransaction: async (tx: T) => tx, +} +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function stubChain( + administrator = CURRENT_ADMIN, + onAddress?: (address: string) => void, + pendingAdministrator?: string, +): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + getTokenAdminRegistryFor: async (address: string) => { + onAddress?.(address) + return ROUTER + }, + getRegistryTokenConfig: async () => ({ administrator, pendingAdministrator }), + } as unknown as SolanaChain +} + +function generate(opts: Partial = {}) { + return new TransferAdmin().generate(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + newAdmin: NEW_ADMIN, + payer: PAYER, + authority: CURRENT_ADMIN, + ...opts, + }) +} + +describe('TransferAdmin (cct/solana)', () => { + describe('generate', () => { + it('builds an unsigned transfer admin instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + + assert.ok(instruction) + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction.programId.toBase58(), ROUTER) + assert.equal(instruction.data.subarray(0, 8).toString('hex'), 'b262cbb5cb6b6a0e') + assert.deepEqual(instruction.data.subarray(8), new PublicKey(NEW_ADMIN).toBuffer()) + assert.deepEqual( + instruction.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveRouterConfigPda(new PublicKey(ROUTER)).toBase58(), + isSigner: false, + isWritable: false, + }, + { + pubkey: deriveTokenAdminRegistryPda( + new PublicKey(ROUTER), + new PublicKey(TOKEN), + ).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: TOKEN, isSigner: false, isWritable: false }, + { pubkey: CURRENT_ADMIN, isSigner: true, isWritable: true }, + ], + ) + }) + + it('resolves the router from address', async () => { + let requestedAddress: string | undefined + await new TransferAdmin().generate( + stubChain(CURRENT_ADMIN, (address) => (requestedAddress = address)), + { + tokenAddress: TOKEN, + address: ADDRESS, + newAdmin: NEW_ADMIN, + payer: PAYER, + authority: CURRENT_ADMIN, + }, + ) + + assert.equal(requestedAddress, ADDRESS) + }) + }) + + describe('validation', () => { + it('rejects an authority that is not the current administrator', async () => { + await assert.rejects( + () => generate({ authority: PAYER }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'authority', + ) + }) + + it('requires a pending admin to accept the initial registration before transferring', async () => { + await assert.rejects( + () => + new TransferAdmin().generate( + stubChain(PublicKey.default.toBase58(), undefined, CURRENT_ADMIN), + { + tokenAddress: TOKEN, + address: ADDRESS, + newAdmin: NEW_ADMIN, + payer: PAYER, + authority: CURRENT_ADMIN, + }, + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'authority' && + typeof err.context.reason === 'string' && + err.context.reason.includes('still pending acceptance'), + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + assert.deepEqual( + await new TransferAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + newAdmin: NEW_ADMIN, + wallet: SUBMIT_WALLET, + }), + { hash: HASH }, + ) + }) + + it('requires the current admin to be the executing wallet', async () => { + await assert.rejects( + () => + new TransferAdmin().execute(stubChain(), { + tokenAddress: TOKEN, + address: ADDRESS, + newAdmin: NEW_ADMIN, + authority: CURRENT_ADMIN, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'authority' && + typeof err.context.reason === 'string' && + err.context.reason.includes('requires authority to be the executing wallet'), + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-admin-registry/operations/transfer-admin.ts b/ccip-sdk/src/cct/solana/token-admin-registry/operations/transfer-admin.ts new file mode 100644 index 000000000..3b125e1ab --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-admin-registry/operations/transfer-admin.ts @@ -0,0 +1,132 @@ +import { PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + createRouterProgram, + deriveRouterConfigPda, + deriveTokenAdminRegistryPda, +} from '../../programs/router.ts' +import { submit } from '../../submit.ts' +import { parsePublicKey, validateAuthorityMatchesWallet } from '../../validate.ts' + +/** Parameters shared by Solana TokenAdminRegistry `transferAdmin` generation and execution. */ +type TransferAdminParams = { + tokenAddress: string + /** + * CCIP contract to resolve the TokenAdminRegistry/Router from — a Router or OffRamp + * address works. + */ + address: string + /** The administrator proposed to accept the token's registry admin role. */ + newAdmin: string + /** Current token admin. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +/** Parameters for unsigned Solana TokenAdminRegistry `transferAdmin` generation. */ +export type GenerateTransferAdminParams = SolanaGenerateParams + +type ParsedTransferAdminParams = { + tokenMint: PublicKey + address: PublicKey + newAdmin: PublicKey + payer: PublicKey + authority: PublicKey +} + +/** Unsigned Solana TokenAdminRegistry `transferAdmin` result. */ +export type GenerateTransferAdminResult = UnsignedSolanaTx + +/** Parameters for executing Solana TokenAdminRegistry `transferAdmin`. */ +export type ExecuteTransferAdminParams = SolanaExecuteParams + +/** Result of executing Solana TokenAdminRegistry `transferAdmin`. */ +export type ExecuteTransferAdminResult = TransactionResult + +/** Transfers a TokenAdminRegistry administrator role. The proposed admin must accept separately. */ +export class TransferAdmin extends SolanaOperation< + TransferAdminParams, + UnsignedSolanaTx, + ParsedTransferAdminParams +> { + readonly name = 'transferAdmin' + + /** Parses all public keys before any RPC. */ + protected override parse(params: GenerateTransferAdminParams): ParsedTransferAdminParams { + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenMint: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + address: parsePublicKey(this.name, 'address', params.address), + newAdmin: parsePublicKey(this.name, 'newAdmin', params.newAdmin), + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + } + } + + /** Builds the unsigned instruction after confirming the caller is the current admin. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedTransferAdminParams, + ): Promise { + const { tokenMint, payer, authority, newAdmin } = opts + const router = new PublicKey(await chain.getTokenAdminRegistryFor(opts.address.toBase58())) + const tokenConfig = await chain.getRegistryTokenConfig(router.toBase58(), tokenMint.toBase58()) + + if (!new PublicKey(tokenConfig.administrator).equals(authority)) { + const pending = tokenConfig.pendingAdministrator + throw new CCTParamsInvalidError( + this.name, + 'authority', + PublicKey.default.toBase58() === tokenConfig.administrator && pending + ? `registration for this token is still pending acceptance by ${pending}; the pending administrator must accept the admin role first — this operation only transfers an accepted role` + : `must be the current token administrator (${tokenConfig.administrator})`, + ) + } + + const instruction = await createRouterProgram(chain, router, payer) + .methods.transferAdminRoleTokenAdminRegistry(newAdmin) + .accounts({ + config: deriveRouterConfigPda(router), + tokenAdminRegistry: deriveTokenAdminRegistryPda(router, tokenMint), + mint: tokenMint, + authority, + }) + .instruction() + + chain.logger.debug( + `${this.name}: router = ${router.toBase58()}, token = ${tokenMint.toBase58()}, newAdmin = ${newAdmin.toBase58()}`, + ) + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the current admin wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteTransferAdminParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'transferAdmin requires authority to be the executing wallet. Use generateUnsignedTransferAdmin for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/accept-ownership.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/accept-ownership.test.ts new file mode 100644 index 000000000..4796f528a --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/accept-ownership.test.ts @@ -0,0 +1,198 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { BorshAccountsCoder } from '@coral-xyz/anchor' +import { Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import { tokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveTokenPoolConfigPda, resolveTokenPoolProgram } from '../../programs/token-pool.ts' +import { AcceptOwnership } from './accept-ownership.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function stateData(proposedOwner = AUTHORITY): Buffer { + const key = PublicKey.default.toBuffer() + return Buffer.concat([ + BorshAccountsCoder.accountDiscriminator('State'), + Buffer.from([1]), + key, + new PublicKey(TOKEN).toBuffer(), + Buffer.from([6]), + key, + key, + key, + new PublicKey(proposedOwner).toBuffer(), + key, + key, + key, + key, + key, + Buffer.from([0, 0]), + Buffer.alloc(4), + key, + ]) +} + +function chain(proposedOwner = AUTHORITY): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + getAccountInfo: async () => ({ owner: PublicKey.default, data: stateData(proposedOwner) }), + }, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(chain(WALLET.publicKey.toBase58()), { + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + getAccountInfo: async () => ({ + owner: PublicKey.default, + data: stateData(WALLET.publicKey.toBase58()), + }), + }, + }) +} + +function generate(opts = {}) { + return new AcceptOwnership().generate(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + authority: AUTHORITY, + ...opts, + }) +} + +describe('AcceptOwnership (cct/solana)', () => { + describe('generate', () => { + it('builds the ownership-acceptance instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + const poolProgram = resolveTokenPoolProgram('burn-mint') + const decoded = tokenPoolCoder.instruction.decode(instruction!.data) + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction!.programId.toBase58(), poolProgram.toBase58()) + assert.deepEqual( + instruction!.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveTokenPoolConfigPda(poolProgram, new PublicKey(TOKEN)).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: TOKEN, isSigner: false, isWritable: false }, + { pubkey: AUTHORITY, isSigner: true, isWritable: false }, + ], + ) + assert.ok(decoded) + assert.equal(decoded.name, 'acceptOwnership') + }) + + it('defaults authority to payer', async () => { + const unsigned = await new AcceptOwnership().generate(chain(PAYER), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + }) + + assert.equal(unsigned.instructions[0]!.keys[2]!.pubkey.toBase58(), PAYER) + }) + + it('supports a compatible custom pool program', async () => { + const poolProgramAddress = Keypair.generate().publicKey.toBase58() + const unsigned = await generate({ poolType: undefined, poolProgramAddress }) + + assert.equal(unsigned.instructions[0]?.programId.toBase58(), poolProgramAddress) + }) + }) + + describe('validation', () => { + it('rejects an authority that is not the proposed owner', async () => { + await assert.rejects( + () => generate({ authority: PAYER }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'authority' && + err.message.includes('must be the proposed owner'), + ) + }) + + it('rejects when there is no proposed owner', async () => { + await assert.rejects( + () => + new AcceptOwnership().generate(chain(PublicKey.default.toBase58()), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'authority' && + err.message.includes('no proposed owner'), + ) + }) + + it('rejects invalid public keys', async () => { + for (const [opts, param] of [ + [{ tokenAddress: 'invalid' }, 'tokenAddress'], + [{ authority: 'invalid' }, 'authority'], + ]) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new AcceptOwnership().execute(submitChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-wallet authority for signed acceptance', async () => { + await assert.rejects( + () => + new AcceptOwnership().execute(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + authority: AUTHORITY, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'acceptOwnership' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/accept-ownership.ts b/ccip-sdk/src/cct/solana/token-pool/operations/accept-ownership.ts new file mode 100644 index 000000000..923ea5665 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/accept-ownership.ts @@ -0,0 +1,125 @@ +import { PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type PoolProgramRef, + createTokenPoolProgram, + deriveTokenPoolConfigPda, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + parsePublicKey, + resolvePoolProgram, + validateAuthorityMatchesWallet, +} from '../../validate.ts' +import { GetTokenPoolState } from './get-token-pool-state.ts' + +/** Parameters shared by Solana token pool ownership-acceptance generation and execution. */ +type AcceptOwnershipParams = PoolProgramRef & { + /** Token mint address managed by the pool. */ + tokenAddress: string + /** Proposed pool owner accepting ownership. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type ParsedAcceptOwnershipParams = { + tokenAddress: PublicKey + poolProgram: PublicKey + payer: PublicKey + authority: PublicKey +} + +/** Parameters for unsigned Solana token pool ownership acceptance. */ +export type GenerateAcceptOwnershipParams = SolanaGenerateParams + +/** Unsigned Solana token pool ownership acceptance result. */ +export type GenerateAcceptOwnershipResult = UnsignedSolanaTx + +/** Parameters for executing Solana token pool ownership acceptance. */ +export type ExecuteAcceptOwnershipParams = SolanaExecuteParams + +/** Result of executing Solana token pool ownership acceptance. */ +export type ExecuteAcceptOwnershipResult = TransactionResult + +/** Accepts pending ownership of a Solana token pool. */ +export class AcceptOwnership extends SolanaOperation< + AcceptOwnershipParams, + UnsignedSolanaTx, + ParsedAcceptOwnershipParams +> { + readonly name = 'acceptOwnership' + + /** Parses public keys and defaults authority to payer without mutating caller params. */ + protected override parse(params: GenerateAcceptOwnershipParams): ParsedAcceptOwnershipParams { + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + poolProgram: resolvePoolProgram(this.name, params), + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + } + } + + /** Confirms the authority is the proposed owner, then builds the unsigned `acceptOwnership` instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedAcceptOwnershipParams, + ): Promise { + const { config } = await new GetTokenPoolState().query(chain, { + tokenAddress: opts.tokenAddress.toBase58(), + poolProgramAddress: opts.poolProgram.toBase58(), + }) + const proposedOwner = new PublicKey(config.proposedOwner) + if (proposedOwner.equals(PublicKey.default)) { + throw new CCTParamsInvalidError(this.name, 'authority', 'no proposed owner') + } + if (!proposedOwner.equals(opts.authority)) { + throw new CCTParamsInvalidError(this.name, 'authority', 'must be the proposed owner') + } + + const instruction = await createTokenPoolProgram(chain, opts.poolProgram, opts.payer) + .methods.acceptOwnership() + .accountsStrict({ + state: deriveTokenPoolConfigPda(opts.poolProgram, opts.tokenAddress), + mint: opts.tokenAddress, + authority: opts.authority, + }) + .instruction() + + chain.logger.debug( + `${this.name}: token = ${opts.tokenAddress.toBase58()}, poolProgram = ${opts.poolProgram.toBase58()}`, + ) + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the proposed owner wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteAcceptOwnershipParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'acceptOwnership requires authority to be the executing wallet. Use generateUnsignedAcceptOwnership for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/append-remote-pool-addresses.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/append-remote-pool-addresses.test.ts new file mode 100644 index 000000000..629be5e87 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/append-remote-pool-addresses.test.ts @@ -0,0 +1,171 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey, SystemProgram } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import { tokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { + deriveTokenPoolChainConfigPda, + deriveTokenPoolConfigPda, + resolveTokenPoolProgram, +} from '../../programs/token-pool.ts' +import { AppendRemotePoolAddresses } from './append-remote-pool-addresses.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const SELECTOR = 5009297550715157269n +const REMOTE_POOLS = ['0x1234567890abcdef1234567890abcdef12345678', '0xaabbccdd'] +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function chain(): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: {}, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(chain(), { + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts = {}) { + return new AppendRemotePoolAddresses().generate(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + authority: AUTHORITY, + remoteChainSelector: SELECTOR, + remotePoolAddresses: REMOTE_POOLS, + ...opts, + }) +} + +describe('AppendRemotePoolAddresses (cct/solana)', () => { + describe('generate', () => { + it('builds the append-remote-pool-addresses instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + const poolProgram = resolveTokenPoolProgram('burn-mint') + const decoded = tokenPoolCoder.instruction.decode(instruction!.data) + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction!.programId.toBase58(), poolProgram.toBase58()) + assert.deepEqual( + instruction!.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveTokenPoolConfigPda(poolProgram, new PublicKey(TOKEN)).toBase58(), + isSigner: false, + isWritable: false, + }, + { + pubkey: deriveTokenPoolChainConfigPda( + poolProgram, + SELECTOR, + new PublicKey(TOKEN), + ).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: AUTHORITY, isSigner: true, isWritable: true }, + { pubkey: SystemProgram.programId.toBase58(), isSigner: false, isWritable: false }, + ], + ) + assert.ok(decoded) + assert.equal(decoded.name, 'appendRemotePoolAddresses') + const data = decoded.data as { + remoteChainSelector: { toString(): string } + mint: PublicKey + addresses: { address: Buffer }[] + } + assert.equal(data.remoteChainSelector.toString(), SELECTOR.toString()) + assert.equal(data.mint.toBase58(), TOKEN) + assert.deepEqual( + data.addresses.map(({ address }) => address), + REMOTE_POOLS.map((address) => Buffer.from(address.slice(2), 'hex')), + ) + }) + + it('uses a compatible custom pool program', async () => { + const poolProgramAddress = Keypair.generate().publicKey.toBase58() + const unsigned = await generate({ poolType: undefined, poolProgramAddress }) + + assert.equal(unsigned.instructions[0]?.programId.toBase58(), poolProgramAddress) + }) + }) + + describe('validation', () => { + it('rejects invalid remote pool addresses', async () => { + for (const [opts, param] of [ + [{ remoteChainSelector: 1 }, 'remoteChainSelector'], + [{ remoteChainSelector: -1n }, 'remoteChainSelector'], + [{ remoteChainSelector: 1n << 64n }, 'remoteChainSelector'], + [{ remotePoolAddresses: [] }, 'remotePoolAddresses'], + [{ remotePoolAddresses: [''] }, 'remotePoolAddresses[0]'], + [{ remotePoolAddresses: ['0x123'] }, 'remotePoolAddresses[0]'], + [{ remotePoolAddresses: ['0xaabbccdd', 'aabbccdd'] }, 'remotePoolAddresses[1]'], + [{ remotePoolAddresses: '0x12' }, 'remotePoolAddresses'], + ] as const) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new AppendRemotePoolAddresses().execute(submitChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + remoteChainSelector: SELECTOR, + remotePoolAddresses: REMOTE_POOLS, + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-wallet authority for signed appending', async () => { + await assert.rejects( + () => + new AppendRemotePoolAddresses().execute(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + authority: AUTHORITY, + remoteChainSelector: SELECTOR, + remotePoolAddresses: REMOTE_POOLS, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'appendRemotePoolAddresses' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/append-remote-pool-addresses.ts b/ccip-sdk/src/cct/solana/token-pool/operations/append-remote-pool-addresses.ts new file mode 100644 index 000000000..455a1536d --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/append-remote-pool-addresses.ts @@ -0,0 +1,174 @@ +import type { Buffer } from 'buffer' + +import { type PublicKey, SystemProgram } from '@solana/web3.js' +import BN from 'bn.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type PoolProgramRef, + createTokenPoolProgram, + deriveTokenPoolChainConfigPda, + deriveTokenPoolConfigPda, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + U64_MAX, + parseNonEmptyHexBytes, + parsePublicKey, + resolvePoolProgram, + validateAuthorityMatchesWallet, + validateBigInt, +} from '../../validate.ts' + +/** Parameters shared by Solana remote pool address appending generation and execution. */ +type AppendRemotePoolAddressesParams = PoolProgramRef & { + /** Token mint address managed by the local pool. */ + tokenAddress: string + /** CCIP selector of the remote chain (`u64`). */ + remoteChainSelector: bigint + /** + * Non-empty array of non-empty hex-encoded remote pool addresses, optionally `0x`-prefixed. + * Stored at native byte length; unlike `remoteTokenAddress`, not left-padded to 32 bytes. + */ + remotePoolAddresses: string[] + /** Pool owner. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type ParsedAppendRemotePoolAddressesParams = { + tokenAddress: PublicKey + poolProgram: PublicKey + payer: PublicKey + authority: PublicKey + remoteChainSelector: bigint + remotePoolAddresses: Buffer[] +} + +/** Parameters for unsigned Solana remote pool address appending. */ +export type GenerateAppendRemotePoolAddressesParams = + SolanaGenerateParams + +/** Unsigned Solana remote pool address appending result. */ +export type GenerateAppendRemotePoolAddressesResult = UnsignedSolanaTx + +/** Parameters for executing Solana remote pool address appending. */ +export type ExecuteAppendRemotePoolAddressesParams = + SolanaExecuteParams + +/** Result of executing Solana remote pool address appending. */ +export type ExecuteAppendRemotePoolAddressesResult = TransactionResult + +/** + * Appends remote pool addresses to an initialized remote-chain config. + * + * @remarks Existing addresses are retained. The remote-chain config must already exist. The pool + * rejects addresses already present; duplicate addresses in this request are rejected. To clear + * all pools, use `editChainRemoteConfig` with `remotePoolAddresses: []`. + */ +export class AppendRemotePoolAddresses extends SolanaOperation< + AppendRemotePoolAddressesParams, + UnsignedSolanaTx, + ParsedAppendRemotePoolAddressesParams +> { + readonly name = 'appendRemotePoolAddresses' + + /** Parses addresses and defaults authority to payer without mutating caller params. */ + protected override parse( + params: GenerateAppendRemotePoolAddressesParams, + ): ParsedAppendRemotePoolAddressesParams { + validateBigInt(this.name, 'remoteChainSelector', params.remoteChainSelector, 0n, U64_MAX) + + if (!Array.isArray(params.remotePoolAddresses) || params.remotePoolAddresses.length === 0) { + throw new CCTParamsInvalidError(this.name, 'remotePoolAddresses', 'must be a non-empty array') + } + + const remotePoolAddresses = params.remotePoolAddresses.map((address, i) => + parseNonEmptyHexBytes(this.name, `remotePoolAddresses[${i}]`, address), + ) + const seen = new Set() + + for (const [i, address] of remotePoolAddresses.entries()) { + const hex = address.toString('hex') + if (seen.has(hex)) { + throw new CCTParamsInvalidError( + this.name, + `remotePoolAddresses[${i}]`, + 'must not duplicate a remote pool address', + ) + } + seen.add(hex) + } + + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + poolProgram: resolvePoolProgram(this.name, params), + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + remoteChainSelector: params.remoteChainSelector, + remotePoolAddresses, + } + } + + /** Builds the unsigned Solana `appendRemotePoolAddresses` instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedAppendRemotePoolAddressesParams, + ): Promise { + const program = createTokenPoolProgram(chain, opts.poolProgram, opts.payer) + const instruction = await program.methods + .appendRemotePoolAddresses( + new BN(opts.remoteChainSelector.toString()), + opts.tokenAddress, + opts.remotePoolAddresses.map((address) => ({ address })), + ) + .accountsStrict({ + state: deriveTokenPoolConfigPda(opts.poolProgram, opts.tokenAddress), + chainConfig: deriveTokenPoolChainConfigPda( + opts.poolProgram, + opts.remoteChainSelector, + opts.tokenAddress, + ), + authority: opts.authority, + systemProgram: SystemProgram.programId, + }) + .instruction() + + chain.logger.debug( + `${this.name}: token = ${opts.tokenAddress.toBase58()}, poolProgram = ${opts.poolProgram.toBase58()}, remoteChainSelector = ${opts.remoteChainSelector}`, + ) + + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the pool owner wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteAppendRemotePoolAddressesParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'appendRemotePoolAddresses requires authority to be the executing wallet. Use generateUnsignedAppendRemotePoolAddresses for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/apply-chain-updates.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/apply-chain-updates.test.ts new file mode 100644 index 000000000..0ecc1e04f --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/apply-chain-updates.test.ts @@ -0,0 +1,474 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey } from '@solana/web3.js' + +import { CCIPError } from '../../../../errors/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import { tokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { resolveTokenPoolProgram } from '../../programs/token-pool.ts' +import { ApplyChainUpdates } from './apply-chain-updates.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const SELECTOR = 5009297550715157269n +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function chain(): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: {}, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(chain(), { + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function batchChains() { + return [3n, 4n, 5n].map((remoteChainSelector, i) => ({ + remoteChainSelector, + remoteTokenAddress: `0x${(i + 1).toString(16).padStart(40, '0')}`, + remotePoolAddresses: [`0x${(i + 11).toString(16).padStart(40, '0')}`], + remoteTokenDecimals: 6 + i, + inboundRateLimiterConfig: { enabled: false as const }, + outboundRateLimiterConfig: { enabled: true as const, capacity: 100n, rate: 10n }, + })) +} + +function generateBatches(opts = {}) { + return new ApplyChainUpdates().generateBatch(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + authority: AUTHORITY, + remoteChainSelectorsToRemove: [SELECTOR], + chainsToAdd: [ + { + remoteChainSelector: SELECTOR, + remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + remotePoolAddresses: ['0x1234567890abcdef1234567890abcdef12345678'], + remoteTokenDecimals: 18, + inboundRateLimiterConfig: { enabled: false }, + outboundRateLimiterConfig: { enabled: true, capacity: 100n, rate: 10n }, + }, + ], + ...opts, + }) +} + +async function generate(opts = {}) { + const [unsigned] = await generateBatches(opts) + return unsigned! +} + +describe('ApplyChainUpdates (cct/solana)', () => { + describe('generate', () => { + it('requires the batch API', async () => { + assert.throws( + () => new ApplyChainUpdates().generate(chain(), {} as never), + (error: unknown) => CCIPError.isCCIPError(error) && error.code === 'METHOD_UNSUPPORTED', + ) + assert.throws( + () => new ApplyChainUpdates().execute(chain(), {} as never), + (error: unknown) => CCIPError.isCCIPError(error) && error.code === 'METHOD_UNSUPPORTED', + ) + }) + + it('builds a single unsigned transaction internally', async () => { + const operation = new ApplyChainUpdates() + const params = { + tokenAddress: TOKEN, + poolType: 'burn-mint' as const, + payer: PAYER, + authority: AUTHORITY, + remoteChainSelectorsToRemove: [SELECTOR], + chainsToAdd: [], + } + const unsigned = await (operation as any).buildUnsigned( + chain(), + (operation as any).prepare(params), + ) + + assert.equal(unsigned.instructions.length, 1) + }) + + it('builds delete, initialize, edit, and rate-limit instructions', async () => { + const unsigned = await generate() + const poolProgram = resolveTokenPoolProgram('burn-mint') + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.ok(unsigned.instructions.every(({ programId }) => programId.equals(poolProgram))) + assert.deepEqual( + unsigned.instructions.map( + (instruction) => tokenPoolCoder.instruction.decode(instruction.data)!.name, + ), + [ + 'deleteChainConfig', + 'initChainRemoteConfig', + 'editChainRemoteConfig', + 'setChainRateLimit', + ], + ) + }) + + it('builds delete, then per-chain init, edit, and rate-limit instructions for multiple chains', async () => { + const batches = await generateBatches({ + remoteChainSelectorsToRemove: [1n, 2n], + chainsToAdd: [ + { + remoteChainSelector: 3n, + remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + remotePoolAddresses: ['0x1234567890abcdef1234567890abcdef12345678'], + remoteTokenDecimals: 18, + inboundRateLimiterConfig: { enabled: false }, + outboundRateLimiterConfig: { enabled: true, capacity: 100n, rate: 10n }, + }, + { + remoteChainSelector: 4n, + remoteTokenAddress: '0xaabbccddeeff00112233445566778899aabbccdd', + remotePoolAddresses: [], + remoteTokenDecimals: 6, + inboundRateLimiterConfig: { enabled: true, capacity: 200n, rate: 20n }, + outboundRateLimiterConfig: { enabled: false }, + }, + { + remoteChainSelector: 5n, + remoteTokenAddress: '0x11223344556677889900aabbccddeeff00112233', + remotePoolAddresses: ['0x1234', '0xabcd'], + remoteTokenDecimals: 8, + inboundRateLimiterConfig: { enabled: true, capacity: 5_000n, rate: 50n }, + outboundRateLimiterConfig: { enabled: false }, + }, + ], + }) + + assert.deepEqual( + batches.flatMap((batch) => + batch.instructions.map( + (instruction) => tokenPoolCoder.instruction.decode(instruction.data)!.name, + ), + ), + [ + 'deleteChainConfig', + 'deleteChainConfig', + 'initChainRemoteConfig', + 'editChainRemoteConfig', + 'setChainRateLimit', + 'initChainRemoteConfig', + 'editChainRemoteConfig', + 'setChainRateLimit', + 'initChainRemoteConfig', + 'editChainRemoteConfig', + 'setChainRateLimit', + ], + ) + }) + + it('packs large updates without splitting a chain instruction group', async () => { + const batches = await new ApplyChainUpdates().generateBatch(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + authority: AUTHORITY, + remoteChainSelectorsToRemove: [], + chainsToAdd: batchChains(), + }) + + assert.equal(batches.length, 2) + assert.deepEqual( + batches.flatMap((batch) => + batch.instructions.map( + (instruction) => tokenPoolCoder.instruction.decode(instruction.data)!.name, + ), + ), + [ + 'initChainRemoteConfig', + 'editChainRemoteConfig', + 'setChainRateLimit', + 'initChainRemoteConfig', + 'editChainRemoteConfig', + 'setChainRateLimit', + 'initChainRemoteConfig', + 'editChainRemoteConfig', + 'setChainRateLimit', + ], + ) + assert.ok(batches.every((batch) => batch.instructions.length % 3 === 0)) + }) + + it('rejects a chain update that cannot fit one transaction', async () => { + await assert.rejects( + () => + new ApplyChainUpdates().generateBatch(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + authority: AUTHORITY, + remoteChainSelectorsToRemove: [], + chainsToAdd: [ + { + ...batchChains()[0]!, + remotePoolAddresses: Array.from( + { length: 30 }, + (_, i) => `0x${(i + 1).toString(16).padStart(40, '0')}`, + ), + }, + ], + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'chainsToAdd' && + err.message.includes('chain selector 0x3 (30 remote pool addresses)'), + ) + }) + + it('sets disabled rate-limit configs like EVM applyChainUpdates', async () => { + const unsigned = await generate({ + remoteChainSelectorsToRemove: [], + chainsToAdd: [ + { + remoteChainSelector: SELECTOR, + remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + remotePoolAddresses: ['0x1234567890abcdef1234567890abcdef12345678'], + remoteTokenDecimals: 18, + inboundRateLimiterConfig: { enabled: false }, + outboundRateLimiterConfig: { enabled: false }, + }, + ], + }) + + const decoded = tokenPoolCoder.instruction.decode(unsigned.instructions[2]!.data) + + assert.equal(unsigned.instructions.length, 3) + assert.ok(decoded) + assert.equal(decoded.name, 'setChainRateLimit') + }) + + it('uses a compatible custom pool program', async () => { + const poolProgramAddress = Keypair.generate().publicKey.toBase58() + const unsigned = await generate({ poolType: undefined, poolProgramAddress }) + + assert.ok( + unsigned.instructions.every(({ programId }) => programId.toBase58() === poolProgramAddress), + ) + }) + }) + + describe('validation', () => { + it('rejects invalid chain updates', async () => { + for (const [opts, param] of [ + [{ remoteChainSelectorsToRemove: null }, 'remoteChainSelectorsToRemove'], + [{ remoteChainSelectorsToRemove: [-1n] }, 'remoteChainSelector'], + [{ chainsToAdd: null }, 'chainsToAdd'], + [{ chainsToAdd: [], remoteChainSelectorsToRemove: [] }, 'chainsToAdd'], + [{ chainsToAdd: [null] }, 'chainsToAdd[0]'], + [{ remoteChainSelectorsToRemove: [SELECTOR, SELECTOR] }, 'remoteChainSelectorsToRemove[1]'], + [ + { + chainsToAdd: [ + { + remoteChainSelector: SELECTOR, + remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + remotePoolAddresses: [], + remoteTokenDecimals: 18, + inboundRateLimiterConfig: { enabled: false }, + outboundRateLimiterConfig: { enabled: false }, + }, + { + remoteChainSelector: SELECTOR, + remoteTokenAddress: '0xaabbccddeeff00112233445566778899aabbccdd', + remotePoolAddresses: [], + remoteTokenDecimals: 18, + inboundRateLimiterConfig: { enabled: false }, + outboundRateLimiterConfig: { enabled: false }, + }, + ], + }, + 'chainsToAdd[1]', + ], + [ + { + chainsToAdd: [ + { + remoteChainSelector: SELECTOR, + remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + remotePoolAddresses: [], + remoteTokenDecimals: 256, + inboundRateLimiterConfig: { enabled: false }, + outboundRateLimiterConfig: { enabled: false }, + }, + ], + }, + 'remoteTokenDecimals', + ], + [ + { + chainsToAdd: [ + { + remoteChainSelector: SELECTOR, + remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + remotePoolAddresses: null, + remoteTokenDecimals: 18, + inboundRateLimiterConfig: { enabled: false }, + outboundRateLimiterConfig: { enabled: false }, + }, + ], + }, + 'remotePoolAddresses', + ], + [ + { + chainsToAdd: [ + { + remoteChainSelector: SELECTOR, + remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + remotePoolAddresses: ['0x'], + remoteTokenDecimals: 18, + inboundRateLimiterConfig: { enabled: false }, + outboundRateLimiterConfig: { enabled: false }, + }, + ], + }, + 'chainsToAdd[0].remotePoolAddresses[0]', + ], + [ + { + chainsToAdd: [ + { + remoteChainSelector: SELECTOR, + remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + remotePoolAddresses: ['0x1234', '0x1234'], + remoteTokenDecimals: 18, + inboundRateLimiterConfig: { enabled: false }, + outboundRateLimiterConfig: { enabled: false }, + }, + ], + }, + 'chainsToAdd[0].remotePoolAddresses[1]', + ], + [ + { + chainsToAdd: [ + { + remoteChainSelector: SELECTOR, + remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + remotePoolAddresses: [], + remoteTokenDecimals: 18, + inboundRateLimiterConfig: { enabled: false, capacity: 1n, rate: 0n }, + outboundRateLimiterConfig: { enabled: false }, + }, + ], + }, + 'inbound', + ], + ] as const) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + }) + + describe('execute', () => { + it('signs, submits, and returns all tx hashes', async () => { + const result = await new ApplyChainUpdates().executeBatch(submitChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + remoteChainSelectorsToRemove: [SELECTOR], + chainsToAdd: [ + { + remoteChainSelector: SELECTOR, + remoteTokenAddress: '0x1234567890abcdef1234567890abcdef12345678', + remotePoolAddresses: ['0x1234567890abcdef1234567890abcdef12345678'], + remoteTokenDecimals: 18, + inboundRateLimiterConfig: { enabled: false }, + outboundRateLimiterConfig: { enabled: true, capacity: 100n, rate: 10n }, + }, + ], + wallet: WALLET, + }) + + assert.deepEqual(result, { hashes: [HASH], chainSelectors: [[`0x${SELECTOR.toString(16)}`]] }) + }) + + it('submits every safely packed batch and returns all hashes', async () => { + const result = await new ApplyChainUpdates().executeBatch(submitChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + remoteChainSelectorsToRemove: [], + chainsToAdd: batchChains(), + wallet: WALLET, + }) + + assert.deepEqual(result, { hashes: [HASH, HASH], chainSelectors: [['0x3', '0x4'], ['0x5']] }) + }) + + it('attaches committed hashes when a later batch fails', async () => { + let simulations = 0 + const failedChain = submitChain() + failedChain.connection.simulateTransaction = (async () => { + simulations++ + return { + value: { err: simulations >= 2 ? { custom: 1 } : null, logs: [], unitsConsumed: 1 }, + } + }) as never + + await assert.rejects( + () => + new ApplyChainUpdates().executeBatch(failedChain, { + tokenAddress: TOKEN, + poolType: 'burn-mint', + remoteChainSelectorsToRemove: [], + chainsToAdd: batchChains(), + wallet: WALLET, + }), + (error: unknown) => + CCIPError.isCCIPError(error) && + error.context.committedHashes instanceof Array && + error.context.committedHashes[0] === HASH && + error.context.committedChainSelectors instanceof Array && + error.context.committedChainSelectors[0]?.join() === '0x3,0x4' && + error.context.failedBatchIndex === 1 && + error.context.totalBatches === 2, + ) + }) + + it('rejects a non-wallet authority for signed configuration', async () => { + await assert.rejects( + () => + new ApplyChainUpdates().executeBatch(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + authority: AUTHORITY, + remoteChainSelectorsToRemove: [SELECTOR], + chainsToAdd: [], + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'applyChainUpdates' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/apply-chain-updates.ts b/ccip-sdk/src/cct/solana/token-pool/operations/apply-chain-updates.ts new file mode 100644 index 000000000..fd8eafd14 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/apply-chain-updates.ts @@ -0,0 +1,403 @@ +import { + type TransactionInstruction, + ComputeBudgetProgram, + PublicKey, + TransactionMessage, + VersionedTransaction, +} from '@solana/web3.js' + +import { CCIPError, CCIPMethodUnsupportedError } from '../../../../errors/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import type { PoolProgramRef } from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + parseNonEmptyHexBytes, + parsePublicKey, + resolvePoolProgram, + validateAuthorityMatchesWallet, + validateUniqueChainSelectors, +} from '../../validate.ts' +import { DeleteChainRemoteConfig } from './delete-chain-remote-config.ts' +import { EditChainRemoteConfig } from './edit-chain-remote-config.ts' +import { InitChainRemoteConfig } from './init-chain-remote-config.ts' +import { type RateLimitConfig, SetChainRateLimit } from './set-chain-rate-limit.ts' + +const MAX_TRANSACTION_SIZE = 1232 + +type InstructionGroup = { + instructions: TransactionInstruction[] + remoteChainSelector?: bigint + remotePoolCount?: number +} + +type PackedInstructionGroup = { + transaction: UnsignedSolanaTx + chainSelectors: string[] +} + +/** A remote-chain configuration to add, matching the EVM `ChainUpdate` fields plus Solana decimals. */ +type ChainUpdate = { + /** CCIP selector of the remote chain (`u64`). */ + remoteChainSelector: bigint + /** Hex-encoded remote token address, optionally `0x`-prefixed, up to 32 bytes. */ + remoteTokenAddress: string + /** Hex-encoded remote pool addresses, optionally `0x`-prefixed; supplied addresses are non-empty and unique. */ + remotePoolAddresses: string[] + /** Remote token decimals (`u8`), required by the Solana pool account. */ + remoteTokenDecimals: number + /** Rate limit for tokens received from the remote chain. */ + inboundRateLimiterConfig: RateLimitConfig + /** Rate limit for tokens sent to the remote chain. */ + outboundRateLimiterConfig: RateLimitConfig +} + +type ApplyChainUpdatesParams = PoolProgramRef & { + /** Token mint address managed by the local pool. */ + tokenAddress: string + /** + * Remote chain configurations to add, including their rate limits. To replace a config, include + * its selector here and in `remoteChainSelectorsToRemove`. + */ + chainsToAdd: ChainUpdate[] + /** Remote chain configurations to delete before additions are initialized. */ + remoteChainSelectorsToRemove: bigint[] + /** Pool owner. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type PoolInstructionParams = PoolProgramRef & { + tokenAddress: string + payer: string + authority: string +} + +type ParsedApplyChainUpdatesParams = ApplyChainUpdatesParams & { + payer: string + authority: string +} + +function validateRemotePoolAddresses(operation: string, updates: unknown[]): void { + for (const [i, update] of updates.entries()) { + if (typeof update !== 'object' || update === null) { + throw new CCTParamsInvalidError(operation, `chainsToAdd[${i}]`, 'must be a chain update') + } + const remotePoolAddresses = (update as { remotePoolAddresses?: unknown }).remotePoolAddresses + if (!Array.isArray(remotePoolAddresses)) continue + + const pools = new Set() + for (const [j, address] of remotePoolAddresses.entries()) { + const parsed = parseNonEmptyHexBytes( + operation, + `chainsToAdd[${i}].remotePoolAddresses[${j}]`, + address, + ) + if (pools.has(parsed.toString('hex'))) { + throw new CCTParamsInvalidError( + operation, + `chainsToAdd[${i}].remotePoolAddresses[${j}]`, + 'must not duplicate a remote pool address', + ) + } + pools.add(parsed.toString('hex')) + } + } +} + +/** Serializes a conservative v0 transaction, including compute-budget overhead, to check its size. */ +function fitsInTransaction(payer: PublicKey, instructions: TransactionInstruction[]): boolean { + try { + const transaction = new VersionedTransaction( + new TransactionMessage({ + payerKey: payer, + recentBlockhash: PublicKey.default.toBase58(), + instructions: [ + // submit may add this instruction after simulation; include it so batches remain safe. + ComputeBudgetProgram.setComputeUnitLimit({ units: 1_400_000 }), + ...instructions, + ], + }).compileToV0Message(), + ) + return transaction.serialize().length <= MAX_TRANSACTION_SIZE + } catch { + return false + } +} + +/** Packs ordered instruction groups without splitting a remote-chain update across transactions. */ +function packInstructionGroups( + operation: string, + payer: PublicKey, + groups: InstructionGroup[], +): PackedInstructionGroup[] { + const batches: PackedInstructionGroup[] = [] + let instructions: TransactionInstruction[] = [] + let chainSelectors: string[] = [] + + for (const group of groups) { + if (!fitsInTransaction(payer, group.instructions)) { + const detail = + group.remoteChainSelector === undefined + ? 'a delete' + : `chain selector 0x${group.remoteChainSelector.toString(16)} (${group.remotePoolCount} remote pool addresses)` + throw new CCTParamsInvalidError( + operation, + 'chainsToAdd', + `${detail} exceeds Solana's ${MAX_TRANSACTION_SIZE}-byte transaction limit`, + ) + } + if ( + instructions.length && + !fitsInTransaction(payer, [...instructions, ...group.instructions]) + ) { + batches.push({ + transaction: { family: ChainFamily.Solana, instructions, mainIndex: 0 }, + chainSelectors, + }) + instructions = [] + chainSelectors = [] + } + instructions.push(...group.instructions) + if (group.remoteChainSelector !== undefined) { + chainSelectors.push(`0x${group.remoteChainSelector.toString(16)}`) + } + } + + if (instructions.length) { + batches.push({ + transaction: { family: ChainFamily.Solana, instructions, mainIndex: 0 }, + chainSelectors, + }) + } + return batches +} + +/** Parameters for unsigned Solana token pool chain updates. */ +export type GenerateApplyChainUpdatesParams = SolanaGenerateParams + +/** Unsigned Solana token pool chain updates result. */ +export type GenerateApplyChainUpdatesResult = UnsignedSolanaTx[] + +/** Parameters for executing Solana token pool chain updates. */ +export type ExecuteApplyChainUpdatesParams = SolanaExecuteParams + +/** All confirmed transaction hashes for Solana token pool chain updates. */ +export type ExecuteApplyChainUpdatesResult = { hashes: string[]; chainSelectors: string[][] } + +/** + * Applies the EVM `applyChainUpdates` equivalent as Solana instructions. + * + * @remarks + * Chain selectors to add and remove must not contain duplicates. This preserves EVM ordering: all + * removals run first, then each added chain is initialized, + * configured with remote pools, and assigned both rate-limit configs. EVM-style replacement is + * supported by listing a selector in both `remoteChainSelectorsToRemove` and `chainsToAdd`; + * adding an existing selector without removing it fails. Updates are packed into one or more + * transactions, keeping each chain's initialization, configuration, and rate-limit instructions + * together. Batches are submitted sequentially; a later failure leaves earlier batches committed. + */ +export class ApplyChainUpdates extends SolanaOperation< + ApplyChainUpdatesParams, + UnsignedSolanaTx, + ParsedApplyChainUpdatesParams +> { + readonly name = 'applyChainUpdates' + + /** Validates the batch envelope; component operations validate each chain update. */ + protected override parse(params: GenerateApplyChainUpdatesParams): ParsedApplyChainUpdatesParams { + parsePublicKey(this.name, 'tokenAddress', params.tokenAddress) + parsePublicKey(this.name, 'payer', params.payer) + resolvePoolProgram(this.name, params) + if (!Array.isArray(params.chainsToAdd)) { + throw new CCTParamsInvalidError(this.name, 'chainsToAdd', 'must be an array') + } + if (!Array.isArray(params.remoteChainSelectorsToRemove)) { + throw new CCTParamsInvalidError(this.name, 'remoteChainSelectorsToRemove', 'must be an array') + } + if (!params.chainsToAdd.length && !params.remoteChainSelectorsToRemove.length) { + throw new CCTParamsInvalidError( + this.name, + 'chainsToAdd', + 'at least one of chainsToAdd or remoteChainSelectorsToRemove must be non-empty', + ) + } + validateUniqueChainSelectors( + this.name, + 'remoteChainSelectorsToRemove', + params.remoteChainSelectorsToRemove, + ) + const chainsToAdd: unknown[] = params.chainsToAdd + validateUniqueChainSelectors( + this.name, + 'chainsToAdd', + chainsToAdd.map((update) => + typeof update === 'object' && update !== null + ? (update as { remoteChainSelector?: unknown }).remoteChainSelector + : undefined, + ), + ) + validateRemotePoolAddresses(this.name, chainsToAdd) + + return { + ...params, + authority: + params.authority === undefined + ? params.payer + : parsePublicKey(this.name, 'authority', params.authority).toBase58(), + } + } + + /** Builds the initialize, edit, and rate-limit instructions for one added chain. */ + private async buildAddInstructions( + chain: SolanaChain, + pool: PoolInstructionParams, + update: ChainUpdate, + ): Promise { + const config = { + ...pool, + remoteChainSelector: update.remoteChainSelector, + remoteTokenAddress: update.remoteTokenAddress, + remotePoolAddresses: update.remotePoolAddresses, + remoteTokenDecimals: update.remoteTokenDecimals, + } + const init = await new InitChainRemoteConfig().generate(chain, config) + const edit = await new EditChainRemoteConfig().generate(chain, config) + const rateLimit = await new SetChainRateLimit().generate(chain, { + ...pool, + remoteChainSelector: update.remoteChainSelector, + inbound: update.inboundRateLimiterConfig, + outbound: update.outboundRateLimiterConfig, + }) + return [...init.instructions, ...edit.instructions, ...rateLimit.instructions] + } + + /** Builds ordered delete and per-chain update instruction groups. */ + private async buildInstructionGroups( + chain: SolanaChain, + params: ParsedApplyChainUpdatesParams, + ): Promise { + const pool: PoolInstructionParams = { + tokenAddress: params.tokenAddress, + payer: params.payer, + authority: params.authority, + ...(params.poolType === undefined + ? { poolProgramAddress: params.poolProgramAddress } + : { poolType: params.poolType }), + } + const groups: InstructionGroup[] = [] + + for (const remoteChainSelector of params.remoteChainSelectorsToRemove) { + const tx = await new DeleteChainRemoteConfig().generate(chain, { + ...pool, + remoteChainSelector, + }) + groups.push({ instructions: tx.instructions }) + } + for (const update of params.chainsToAdd) { + groups.push({ + instructions: await this.buildAddInstructions(chain, pool, update), + remoteChainSelector: update.remoteChainSelector, + remotePoolCount: update.remotePoolAddresses.length, + }) + } + return groups + } + + /** Builds all instructions in contract-equivalent order as one unsigned transaction. */ + protected async buildUnsigned( + chain: SolanaChain, + params: ParsedApplyChainUpdatesParams, + ): Promise { + return { + family: ChainFamily.Solana, + instructions: (await this.buildInstructionGroups(chain, params)).flatMap( + (group) => group.instructions, + ), + mainIndex: 0, + } + } + + /** + * Unsupported because this operation may require multiple transactions. + * @see {@link generateBatch}. + */ + override generate( + _chain: SolanaChain, + _params: GenerateApplyChainUpdatesParams, + ): Promise { + throw new CCIPMethodUnsupportedError('ApplyChainUpdates', 'generate; use generateBatch') + } + + /** Builds one or more ordered transactions without splitting a per-chain update group. */ + async generateBatch( + chain: SolanaChain, + params: GenerateApplyChainUpdatesParams, + ): Promise { + const parsed = this.prepare(params) + return packInstructionGroups( + this.name, + new PublicKey(parsed.payer), + await this.buildInstructionGroups(chain, parsed), + ).map(({ transaction }) => transaction) + } + + /** + * Unsupported because this operation may require multiple transactions. + * @see {@link executeBatch}. + */ + override execute( + _chain: SolanaChain, + _params: ExecuteApplyChainUpdatesParams, + ): Promise { + throw new CCIPMethodUnsupportedError('ApplyChainUpdates', 'execute; use executeBatch') + } + + /** Signs, submits, and confirms each packed transaction, returning every transaction hash. */ + async executeBatch( + chain: SolanaChain, + params: ExecuteApplyChainUpdatesParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + validateAuthorityMatchesWallet( + this.name, + new PublicKey(parsed.authority), + wallet.publicKey, + 'applyChainUpdates requires authority to be the executing wallet. Use generateUnsignedApplyChainUpdates for externally signed transactions.', + ) + + const batches = packInstructionGroups( + this.name, + wallet.publicKey, + await this.buildInstructionGroups(chain, parsed), + ) + const hashes: string[] = [] + const chainSelectors: string[][] = [] + + for (const [failedBatchIndex, batch] of batches.entries()) { + try { + hashes.push((await submit(chain, wallet, batch.transaction, this.name, computeUnits)).hash) + chainSelectors.push(batch.chainSelectors) + } catch (error) { + if (CCIPError.isCCIPError(error)) { + Object.assign(error.context, { + committedHashes: hashes, + committedChainSelectors: chainSelectors, + failedBatchIndex, + totalBatches: batches.length, + }) + } + throw error + } + } + + return { hashes, chainSelectors } + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/configure-allowlist.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/configure-allowlist.test.ts new file mode 100644 index 000000000..d344e93c7 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/configure-allowlist.test.ts @@ -0,0 +1,220 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey, SystemProgram } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import { tokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveTokenPoolConfigPda, resolveTokenPoolProgram } from '../../programs/token-pool.ts' +import { ConfigureAllowlist } from './configure-allowlist.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const ALLOWED = Keypair.generate().publicKey.toBase58() +const SECOND_ALLOWED = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function stubChain(): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: {}, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(stubChain(), { + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts = {}) { + return new ConfigureAllowlist().generate(stubChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + authority: AUTHORITY, + add: [ALLOWED], + enabled: true, + ...opts, + }) +} + +describe('ConfigureAllowlist (cct/solana)', () => { + describe('generate', () => { + it('builds an unsigned configure allowlist instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + const poolProgram = resolveTokenPoolProgram('burn-mint') + + assert.ok(instruction) + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction.programId.toBase58(), poolProgram.toBase58()) + assert.deepEqual( + instruction.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveTokenPoolConfigPda(poolProgram, new PublicKey(TOKEN)).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: TOKEN, isSigner: false, isWritable: false }, + { pubkey: AUTHORITY, isSigner: true, isWritable: true }, + { pubkey: SystemProgram.programId.toBase58(), isSigner: false, isWritable: false }, + ], + ) + }) + + it('encodes multiple addresses and overwrites enforcement', async () => { + const unsigned = await generate({ add: [ALLOWED, SECOND_ALLOWED], enabled: false }) + const decoded = tokenPoolCoder.instruction.decode(unsigned.instructions[0]!.data) + + assert.ok(decoded) + assert.equal(decoded.name, 'configureAllowList') + assert.deepEqual( + (decoded.data as { add: PublicKey[] }).add.map((address) => address.toBase58()), + [ALLOWED, SECOND_ALLOWED], + ) + assert.equal((decoded.data as { enabled: boolean }).enabled, false) + }) + + it('encodes a toggle without addresses', async () => { + const unsigned = await generate({ add: [], enabled: false }) + const decoded = tokenPoolCoder.instruction.decode(unsigned.instructions[0]!.data) + + assert.ok(decoded) + assert.equal(decoded.name, 'configureAllowList') + assert.deepEqual((decoded.data as { add: PublicKey[] }).add, []) + assert.equal((decoded.data as { enabled: boolean }).enabled, false) + }) + + it('uses a compatible custom pool program', async () => { + const poolProgramAddress = Keypair.generate().publicKey.toBase58() + const unsigned = await new ConfigureAllowlist().generate(stubChain(), { + tokenAddress: TOKEN, + poolProgramAddress, + payer: PAYER, + authority: AUTHORITY, + add: [ALLOWED], + enabled: true, + }) + + assert.equal(unsigned.instructions[0]?.programId.toBase58(), poolProgramAddress) + }) + }) + + describe('validation', () => { + it('rejects invalid pool program references', async () => { + await assert.rejects( + () => generate({ poolType: 'custom' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'configureAllowlist' && + err.context.param === 'poolType', + ) + }) + + it('rejects non-array addresses to add', async () => { + await assert.rejects( + () => generate({ add: 'not-an-array' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'configureAllowlist' && + err.context.param === 'add', + ) + }) + + it('rejects invalid addresses to add', async () => { + await assert.rejects( + () => generate({ add: ['not-a-pubkey'] }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'configureAllowlist' && + err.context.param === 'add[0]', + ) + }) + + it('rejects duplicate addresses to add', async () => { + await assert.rejects( + () => generate({ add: [ALLOWED, ALLOWED] }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'configureAllowlist' && + err.context.param === 'add[1]', + ) + }) + + it('rejects non-boolean enabled values', async () => { + await assert.rejects( + () => generate({ enabled: 'true' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'configureAllowlist' && + err.context.param === 'enabled', + ) + }) + }) + + describe('execute', () => { + it('rejects an invalid wallet', async () => { + await assert.rejects(() => + new ConfigureAllowlist().execute(stubChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + add: [ALLOWED], + enabled: true, + wallet: {} as never, + }), + ) + }) + + it('signs, submits, and returns the tx hash', async () => { + const result = await new ConfigureAllowlist().execute(submitChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + add: [ALLOWED], + enabled: true, + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-wallet authority for signed configuration', async () => { + await assert.rejects( + () => + new ConfigureAllowlist().execute(stubChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + authority: AUTHORITY, + add: [ALLOWED], + enabled: true, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'configureAllowlist' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/configure-allowlist.ts b/ccip-sdk/src/cct/solana/token-pool/operations/configure-allowlist.ts new file mode 100644 index 000000000..22a66a991 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/configure-allowlist.ts @@ -0,0 +1,142 @@ +import { type PublicKey, SystemProgram } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type PoolProgramRef, + createTokenPoolProgram, + deriveTokenPoolConfigPda, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + parsePublicKey, + resolvePoolProgram, + validateAuthorityMatchesWallet, + validateUniquePublicKeys, +} from '../../validate.ts' + +/** Parameters shared by Solana token pool `configureAllowlist` generation and execution. */ +type ConfigureAllowlistParams = PoolProgramRef & { + /** Token mint address managed by the pool. */ + tokenAddress: string + /** Addresses to append to the pool allowlist. Must not contain duplicates. */ + add: string[] + /** Whether the pool should enforce its allowlist. */ + enabled: boolean + /** Pool owner. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type ParsedConfigureAllowlistParams = { + tokenAddress: PublicKey + poolProgram: PublicKey + add: PublicKey[] + enabled: boolean + payer: PublicKey + authority: PublicKey +} + +/** Parameters for unsigned Solana token pool allowlist configuration. */ +export type GenerateConfigureAllowlistParams = SolanaGenerateParams + +/** Unsigned Solana token pool allowlist configuration result. */ +export type GenerateConfigureAllowlistResult = UnsignedSolanaTx + +/** Parameters for executing Solana token pool allowlist configuration. */ +export type ExecuteConfigureAllowlistParams = SolanaExecuteParams + +/** Result of executing Solana token pool allowlist configuration. */ +export type ExecuteConfigureAllowlistResult = TransactionResult + +/** + * Adds addresses to and enables or disables a Solana token pool allowlist. + * @remarks Added addresses must not contain duplicates. + */ +export class ConfigureAllowlist extends SolanaOperation< + ConfigureAllowlistParams, + UnsignedSolanaTx, + ParsedConfigureAllowlistParams +> { + readonly name = 'configureAllowlist' + + /** Parses public keys and defaults authority to payer without mutating caller params. */ + protected override parse( + params: GenerateConfigureAllowlistParams, + ): ParsedConfigureAllowlistParams { + if (!Array.isArray(params.add)) { + throw new CCTParamsInvalidError(this.name, 'add', 'must be an array') + } + if (typeof params.enabled !== 'boolean') { + throw new CCTParamsInvalidError(this.name, 'enabled', 'must be a boolean') + } + + const add = params.add.map((address, index) => + parsePublicKey(this.name, `add[${index}]`, address), + ) + validateUniquePublicKeys(this.name, 'add', add) + + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + poolProgram: resolvePoolProgram(this.name, params), + add, + enabled: params.enabled, + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + } + } + + /** Builds the unsigned Solana `configureAllowList` instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedConfigureAllowlistParams, + ): Promise { + const program = createTokenPoolProgram(chain, opts.poolProgram, opts.payer) + const state = deriveTokenPoolConfigPda(opts.poolProgram, opts.tokenAddress) + + const instruction = await program.methods + .configureAllowList(opts.add, opts.enabled) + .accountsStrict({ + state, + mint: opts.tokenAddress, + authority: opts.authority, + systemProgram: SystemProgram.programId, + }) + .instruction() + + chain.logger.debug( + `${this.name}: token = ${opts.tokenAddress.toBase58()}, poolProgram = ${opts.poolProgram.toBase58()}`, + ) + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the pool owner wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteConfigureAllowlistParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'configureAllowlist requires authority to be the executing wallet. Use generateUnsignedConfigureAllowlist for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/create-token-multisig.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/create-token-multisig.test.ts new file mode 100644 index 000000000..2206615c8 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/create-token-multisig.test.ts @@ -0,0 +1,236 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { MINT_SIZE, MULTISIG_SIZE, MintLayout, TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { Keypair, PublicKey, SystemProgram } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveTokenPoolSignerPda } from '../../programs/token-pool.ts' +import { CreateTokenMultisig } from './create-token-multisig.ts' + +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const MINT = Keypair.generate().publicKey.toBase58() +const POOL_PROGRAM = '41FGToCmdaWa1dgZLKFAjvmx6e6AjVTX7SVRibvsMGVB' +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function mintData(mintAuthority: PublicKey | null = new PublicKey(AUTHORITY)) { + const data = Buffer.alloc(MINT_SIZE) + MintLayout.encode( + { + mintAuthorityOption: mintAuthority ? 1 : 0, + mintAuthority: mintAuthority ?? PublicKey.default, + supply: 0n, + decimals: 0, + isInitialized: true, + freezeAuthorityOption: 0, + freezeAuthority: PublicKey.default, + }, + data, + ) + return data +} + +function stubChain(mintAuthority?: PublicKey | null): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + getAccountInfo: async () => ({ + owner: TOKEN_PROGRAM_ID, + data: mintData(mintAuthority), + executable: false, + lamports: 1, + }), + getMinimumBalanceForRentExemption: async (space: number) => { + assert.equal(space, MULTISIG_SIZE) + return 123 + }, + }, + } as unknown as SolanaChain +} + +function generate(opts = {}) { + return new CreateTokenMultisig().generate(stubChain(), { + tokenAddress: MINT, + poolType: 'burn-mint', + threshold: 2, + payer: PAYER, + seed: 'seed', + ...opts, + }) +} + +describe('CreateTokenMultisig (cct/solana)', () => { + describe('generate', () => { + it('builds a pool-autonomous threshold-two multisig', async () => { + const unsigned = await generate({ + threshold: 2, + additionalSigners: [Keypair.generate().publicKey.toBase58()], + }) + const [createIx, initIx] = unsigned.instructions + assert.ok(createIx) + assert.ok(initIx) + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.match(unsigned.multisigAddress, /^[1-9A-HJ-NP-Za-km-z]+$/) + assert.equal(createIx.programId.toBase58(), SystemProgram.programId.toBase58()) + assert.equal(initIx.programId.toBase58(), TOKEN_PROGRAM_ID.toBase58()) + assert.equal(initIx.data[0], 2) // InitializeMultisig + assert.equal(initIx.data[1], 2) // threshold + + const poolSigner = deriveTokenPoolSignerPda(new PublicKey(POOL_PROGRAM), new PublicKey(MINT)) + assert.equal(initIx.keys.filter((key) => key.pubkey.equals(poolSigner)).length, 2) + assert.ok(initIx.keys.some((key) => key.pubkey.equals(new PublicKey(AUTHORITY)))) + assert.ok(!initIx.keys.some((key) => key.pubkey.equals(new PublicKey(PAYER)))) + }) + + it('builds the canonical threshold-one pool multisig', async () => { + const unsigned = await generate({ threshold: 1 }) + const poolSigner = deriveTokenPoolSignerPda(new PublicKey(POOL_PROGRAM), new PublicKey(MINT)) + + assert.equal(unsigned.instructions[1]!.data[1], 1) + assert.equal( + unsigned.instructions[1]!.keys.filter((key) => key.pubkey.equals(poolSigner)).length, + 1, + ) + }) + + it('adds additional signers', async () => { + const signer = Keypair.generate().publicKey + const unsigned = await generate({ additionalSigners: [signer.toBase58()] }) + + assert.ok(unsigned.instructions[1]!.keys.some((key) => key.pubkey.equals(signer))) + }) + + it('generates a seed when none is supplied', async () => { + assert.ok((await generate({ threshold: 1, seed: undefined })).multisigAddress) + }) + + it('deduplicates a signer that matches the mint authority', async () => { + const unsigned = await generate({ threshold: 1, additionalSigners: [AUTHORITY] }) + + assert.equal( + unsigned.instructions[1]!.keys.filter((key) => key.pubkey.equals(new PublicKey(AUTHORITY))) + .length, + 1, + ) + }) + }) + + describe('validation', () => { + it('rejects non-array additional signers', async () => { + await assert.rejects( + () => generate({ additionalSigners: 'not-an-array' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'additionalSigners', + ) + }) + + it('rejects too many multisig signers', async () => { + await assert.rejects( + () => + generate({ + threshold: 1, + additionalSigners: Array.from({ length: 10 }, () => + Keypair.generate().publicKey.toBase58(), + ), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'additionalSigners', + ) + }) + + it('rejects invalid pool type', async () => { + await assert.rejects( + () => generate({ poolType: 'custom' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'createTokenMultisig' && + err.context.param === 'poolType', + ) + }) + + it('requires an independent governance quorum', async () => { + await assert.rejects( + () => generate(), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'createTokenMultisig' && + err.context.param === 'threshold', + ) + }) + + it('rejects mint without mint authority', async () => { + await assert.rejects( + () => + new CreateTokenMultisig().generate(stubChain(null), { + tokenAddress: MINT, + poolType: 'burn-mint', + threshold: 2, + payer: PAYER, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'createTokenMultisig' && + err.context.param === 'tokenAddress', + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the multisig address', async () => { + const result = await new CreateTokenMultisig().execute( + Object.assign(stubChain(new PublicKey(AUTHORITY)), { + connection: { + getAccountInfo: async () => ({ + owner: TOKEN_PROGRAM_ID, + data: mintData(new PublicKey(AUTHORITY)), + executable: false, + lamports: 1, + }), + getMinimumBalanceForRentExemption: async () => 123, + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }), + { + tokenAddress: MINT, + poolType: 'burn-mint', + threshold: 1, + wallet: { ...WALLET, publicKey: new PublicKey(AUTHORITY) }, + }, + ) + + assert.equal(result.hash, HASH) + assert.ok(result.multisigAddress) + }) + + it('rejects signed execute when wallet is not mint authority', async () => { + await assert.rejects( + () => + new CreateTokenMultisig().execute(stubChain(), { + tokenAddress: MINT, + poolType: 'burn-mint', + threshold: 2, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'createTokenMultisig' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/create-token-multisig.ts b/ccip-sdk/src/cct/solana/token-pool/operations/create-token-multisig.ts new file mode 100644 index 000000000..8eccfb7dd --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/create-token-multisig.ts @@ -0,0 +1,244 @@ +import { MULTISIG_SIZE, createInitializeMultisigInstruction, unpackMint } from '@solana/spl-token' +import { PublicKey, SystemProgram } from '@solana/web3.js' +import { concat, hexlify, randomBytes, sha256, toUtf8Bytes } from 'ethers' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { resolveTokenMint } from '../../../../solana/utils.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type TokenPoolType, + deriveTokenPoolSignerPda, + resolveTokenPoolProgram, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + parsePublicKey, + validateAuthorityMatchesWallet, + validateInteger, + validateNonEmptyString, + validatePoolType, +} from '../../validate.ts' + +export const SOLANA_MULTISIG_MAX_SIGNERS = 11 + +type MintAccount = NonNullable[1]> + +/** + * Parameters for creating an SPL Token multisig account for a Solana SPL mint. + * + * The pool signer PDA occupies `threshold` slots so it can mint autonomously. The mint authority + * read from `tokenAddress` and `additionalSigners` supply the independent signer slots. + */ +type CreateTokenMultisigParams = { + tokenAddress: string + poolType: TokenPoolType + threshold: number + /** Extra multisig member addresses in addition to pool signer PDA and mint authority. */ + additionalSigners?: string[] + /** Optional human seed; internally hashed with mint to fit Solana's 32-byte seed limit. */ + seed?: string +} + +/** Parameters for unsigned Solana token multisig generation. */ +export type GenerateCreateTokenMultisigParams = SolanaGenerateParams + +type ParsedCreateTokenMultisigParams = { + tokenMint: PublicKey + poolProgram: PublicKey + payer: PublicKey + threshold: number + additionalSigners: PublicKey[] + seed?: string +} + +/** Unsigned token multisig transaction plus the created multisig address. */ +export type GenerateCreateTokenMultisigResult = UnsignedSolanaTx & { multisigAddress: string } + +/** Parameters for executing Solana token multisig creation. */ +export type ExecuteCreateTokenMultisigParams = SolanaExecuteParams + +/** Result of executing Solana token multisig creation. */ +export type ExecuteCreateTokenMultisigResult = TransactionResult & { multisigAddress: string } + +function dedupePublicKeys(signers: PublicKey[]) { + const seen = new Set() + return signers.filter((signer) => { + const address = signer.toBase58() + if (seen.has(address)) return false + seen.add(address) + return true + }) +} + +function validatePoolMultisigConfig( + operation: string, + signers: PublicKey[], + poolSigner: PublicKey, + threshold: number, +) { + const poolSignerCount = signers.filter((signer) => signer.equals(poolSigner)).length + const nonPoolSignerCount = signers.length - poolSignerCount + + if (signers.length < 2 || signers.length > SOLANA_MULTISIG_MAX_SIGNERS) { + throw new CCTParamsInvalidError( + operation, + 'additionalSigners', + `multisig must have between 2 and ${SOLANA_MULTISIG_MAX_SIGNERS} total signers`, + ) + } + if (threshold < 1) { + throw new CCTParamsInvalidError(operation, 'threshold', 'must be at least 1') + } + if (threshold > signers.length) { + throw new CCTParamsInvalidError(operation, 'threshold', 'cannot exceed total signer count') + } + if (poolSignerCount < threshold) { + throw new CCTParamsInvalidError( + operation, + 'threshold', + 'pool signer must occupy at least threshold signer slots', + ) + } + if (nonPoolSignerCount < threshold) { + throw new CCTParamsInvalidError( + operation, + 'threshold', + 'requires at least threshold non-pool signers', + ) + } +} + +function getMintAuthority( + operation: string, + tokenMint: PublicKey, + mintAccount: MintAccount, + tokenProgram: PublicKey, +): PublicKey { + const { mintAuthority } = unpackMint(tokenMint, mintAccount, tokenProgram) + if (!mintAuthority) { + throw new CCTParamsInvalidError(operation, 'tokenAddress', 'mint has no mint authority') + } + return new PublicKey(mintAuthority.toBase58()) +} + +/** + * Creates an SPL Token multisig with threshold pool signer slots and independent signers. + * + * The multisig account is derived with `createAccountWithSeed`, so no new signer keypair is needed. + */ +export class CreateTokenMultisig extends SolanaOperation< + CreateTokenMultisigParams, + GenerateCreateTokenMultisigResult, + ParsedCreateTokenMultisigParams +> { + readonly name = 'createTokenMultisig' + + /** Parses public keys, threshold, and optional seed before mint/account RPCs. */ + protected override parse( + params: GenerateCreateTokenMultisigParams, + ): ParsedCreateTokenMultisigParams { + validatePoolType(this.name, 'poolType', params.poolType) + if (params.additionalSigners !== undefined && !Array.isArray(params.additionalSigners)) { + throw new CCTParamsInvalidError(this.name, 'additionalSigners', 'must be an array') + } + validateInteger(this.name, 'threshold', params.threshold, 1, SOLANA_MULTISIG_MAX_SIGNERS) + if (params.seed !== undefined) validateNonEmptyString(this.name, 'seed', params.seed) + + return { + tokenMint: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + poolProgram: resolveTokenPoolProgram(params.poolType), + payer: parsePublicKey(this.name, 'payer', params.payer), + threshold: params.threshold, + additionalSigners: (params.additionalSigners ?? []).map((signer, i) => + parsePublicKey(this.name, `additionalSigners[${i}]`, signer), + ), + ...(params.seed !== undefined && { seed: params.seed }), + } + } + + /** Builds create-with-seed and initialize-multisig instructions. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedCreateTokenMultisigParams, + mintContext?: { account: MintAccount; authority: PublicKey }, + ): Promise { + const { payer, tokenMint, poolProgram } = opts + const mintAccount = + mintContext?.account ?? (await resolveTokenMint(chain.connection, tokenMint)) + + const tokenProgram = mintAccount.owner + const authority = + mintContext?.authority ?? getMintAuthority(this.name, tokenMint, mintAccount, tokenProgram) + + const poolSigner = deriveTokenPoolSignerPda(poolProgram, tokenMint) + const nonPoolSigners = dedupePublicKeys([authority, ...opts.additionalSigners]).filter( + (signer) => !signer.equals(poolSigner), + ) + + const signers = [...Array.from({ length: opts.threshold }, () => poolSigner), ...nonPoolSigners] + validatePoolMultisigConfig(this.name, signers, poolSigner, opts.threshold) + + const seedMaterial = opts.seed ?? hexlify(randomBytes(16)).slice(2) + const seedInput = concat([toUtf8Bytes(seedMaterial), tokenMint.toBuffer()]) + const seedHash = sha256(seedInput) + const seed = seedHash.slice(2, 34) + + const multisig = await PublicKey.createWithSeed(authority, seed, tokenProgram) + const lamports = await chain.connection.getMinimumBalanceForRentExemption(MULTISIG_SIZE) + const createIx = SystemProgram.createAccountWithSeed({ + fromPubkey: payer, + newAccountPubkey: multisig, + basePubkey: authority, + seed, + space: MULTISIG_SIZE, + lamports, + programId: tokenProgram, + }) + const initIx = createInitializeMultisigInstruction( + multisig, + signers, + opts.threshold, + tokenProgram, + ) + + return { + family: ChainFamily.Solana, + instructions: [createIx, initIx], + mainIndex: 0, + multisigAddress: multisig.toBase58(), + } + } + + /** Generate, sign, simulate, send, and return the created multisig address. */ + override async execute( + chain: SolanaChain, + params: ExecuteCreateTokenMultisigParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + const mintAccount = await resolveTokenMint(chain.connection, parsed.tokenMint) + + const tokenProgram = mintAccount.owner + const mintAuthority = getMintAuthority(this.name, parsed.tokenMint, mintAccount, tokenProgram) + validateAuthorityMatchesWallet( + this.name, + mintAuthority, + wallet.publicKey, + 'createTokenMultisig requires the executing wallet to be the mint authority. Use generateUnsignedCreateTokenMultisig for vault-owned mints and have the vault sign/execute it.', + ) + + const tx = await this.buildUnsigned(chain, parsed, { + account: mintAccount, + authority: mintAuthority, + }) + const hash = await submit(chain, wallet, tx, this.name, computeUnits) + return { ...hash, multisigAddress: tx.multisigAddress } + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/delete-chain-remote-config.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/delete-chain-remote-config.test.ts new file mode 100644 index 000000000..068efdec6 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/delete-chain-remote-config.test.ts @@ -0,0 +1,157 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import { tokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { + deriveTokenPoolChainConfigPda, + deriveTokenPoolConfigPda, + resolveTokenPoolProgram, +} from '../../programs/token-pool.ts' +import { DeleteChainRemoteConfig } from './delete-chain-remote-config.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const SELECTOR = 5009297550715157269n +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function chain(): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: {}, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(chain(), { + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts = {}) { + return new DeleteChainRemoteConfig().generate(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + authority: AUTHORITY, + remoteChainSelector: SELECTOR, + ...opts, + }) +} + +describe('DeleteChainRemoteConfig (cct/solana)', () => { + describe('generate', () => { + it('builds the delete-chain-remote-config instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + const poolProgram = resolveTokenPoolProgram('burn-mint') + const decoded = tokenPoolCoder.instruction.decode(instruction!.data) + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction!.programId.toBase58(), poolProgram.toBase58()) + assert.deepEqual( + instruction!.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveTokenPoolConfigPda(poolProgram, new PublicKey(TOKEN)).toBase58(), + isSigner: false, + isWritable: false, + }, + { + pubkey: deriveTokenPoolChainConfigPda( + poolProgram, + SELECTOR, + new PublicKey(TOKEN), + ).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: AUTHORITY, isSigner: true, isWritable: true }, + ], + ) + assert.ok(decoded) + assert.equal(decoded.name, 'deleteChainConfig') + const data = decoded.data as { + remoteChainSelector: { toString(): string } + mint: PublicKey + } + assert.equal(data.remoteChainSelector.toString(), SELECTOR.toString()) + assert.equal(data.mint.toBase58(), TOKEN) + }) + + it('supports a zero remote-chain selector', async () => { + await assert.doesNotReject(() => generate({ remoteChainSelector: 0n })) + }) + + it('uses a compatible custom pool program', async () => { + const poolProgramAddress = Keypair.generate().publicKey.toBase58() + const unsigned = await generate({ poolType: undefined, poolProgramAddress }) + + assert.equal(unsigned.instructions[0]?.programId.toBase58(), poolProgramAddress) + }) + }) + + describe('validation', () => { + it('rejects invalid remote-chain selectors', async () => { + for (const remoteChainSelector of [1, -1n, 1n << 64n] as const) { + await assert.rejects( + () => generate({ remoteChainSelector }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'remoteChainSelector', + ) + } + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new DeleteChainRemoteConfig().execute(submitChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + remoteChainSelector: SELECTOR, + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-wallet authority for signed deletion', async () => { + await assert.rejects( + () => + new DeleteChainRemoteConfig().execute(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + authority: AUTHORITY, + remoteChainSelector: SELECTOR, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'deleteChainRemoteConfig' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/delete-chain-remote-config.ts b/ccip-sdk/src/cct/solana/token-pool/operations/delete-chain-remote-config.ts new file mode 100644 index 000000000..85fa816bb --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/delete-chain-remote-config.ts @@ -0,0 +1,129 @@ +import type { PublicKey } from '@solana/web3.js' +import BN from 'bn.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type PoolProgramRef, + createTokenPoolProgram, + deriveTokenPoolChainConfigPda, + deriveTokenPoolConfigPda, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + U64_MAX, + parsePublicKey, + resolvePoolProgram, + validateAuthorityMatchesWallet, + validateBigInt, +} from '../../validate.ts' + +type DeleteChainRemoteConfigParams = PoolProgramRef & { + /** Token mint address managed by the local pool. */ + tokenAddress: string + /** CCIP selector of the remote chain (`u64`). */ + remoteChainSelector: bigint + /** Pool owner. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type ParsedDeleteChainRemoteConfigParams = { + tokenAddress: PublicKey + poolProgram: PublicKey + payer: PublicKey + authority: PublicKey + remoteChainSelector: bigint +} + +/** Parameters for unsigned Solana token pool remote configuration deletion. */ +export type GenerateDeleteChainRemoteConfigParams = + SolanaGenerateParams + +/** Unsigned Solana token pool remote configuration deletion result. */ +export type GenerateDeleteChainRemoteConfigResult = UnsignedSolanaTx + +/** Parameters for executing Solana token pool remote configuration deletion. */ +export type ExecuteDeleteChainRemoteConfigParams = + SolanaExecuteParams + +/** Result of executing Solana token pool remote configuration deletion. */ +export type ExecuteDeleteChainRemoteConfigResult = TransactionResult + +/** Deletes an initialized remote-chain config. */ +export class DeleteChainRemoteConfig extends SolanaOperation< + DeleteChainRemoteConfigParams, + UnsignedSolanaTx, + ParsedDeleteChainRemoteConfigParams +> { + readonly name = 'deleteChainRemoteConfig' + + /** Parses addresses and defaults authority to payer without mutating caller params. */ + protected override parse( + params: GenerateDeleteChainRemoteConfigParams, + ): ParsedDeleteChainRemoteConfigParams { + validateBigInt(this.name, 'remoteChainSelector', params.remoteChainSelector, 0n, U64_MAX) + + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + poolProgram: resolvePoolProgram(this.name, params), + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + remoteChainSelector: params.remoteChainSelector, + } + } + + /** Builds the unsigned Solana `deleteChainConfig` instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedDeleteChainRemoteConfigParams, + ): Promise { + const program = createTokenPoolProgram(chain, opts.poolProgram, opts.payer) + const instruction = await program.methods + .deleteChainConfig(new BN(opts.remoteChainSelector.toString()), opts.tokenAddress) + .accountsStrict({ + state: deriveTokenPoolConfigPda(opts.poolProgram, opts.tokenAddress), + chainConfig: deriveTokenPoolChainConfigPda( + opts.poolProgram, + opts.remoteChainSelector, + opts.tokenAddress, + ), + authority: opts.authority, + }) + .instruction() + + chain.logger.debug( + `${this.name}: token = ${opts.tokenAddress.toBase58()}, poolProgram = ${opts.poolProgram.toBase58()}, remoteChainSelector = ${opts.remoteChainSelector}`, + ) + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the pool owner wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteDeleteChainRemoteConfigParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'deleteChainRemoteConfig requires authority to be the executing wallet. Use generateUnsignedDeleteChainRemoteConfig for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/deploy-token-pool.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/deploy-token-pool.test.ts new file mode 100644 index 000000000..ccd7080de --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/deploy-token-pool.test.ts @@ -0,0 +1,221 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + getAssociatedTokenAddressSync, +} from '@solana/spl-token' +import { Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveTokenPoolSignerPda, resolveTokenPoolProgram } from '../../index.ts' +import { deriveTokenPoolConfigPda } from '../../programs/token-pool.ts' +import { DeployTokenPool } from './deploy-token-pool.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const BURN_MINT_POOL_PROGRAM = '41FGToCmdaWa1dgZLKFAjvmx6e6AjVTX7SVRibvsMGVB' +const LOCK_RELEASE_POOL_PROGRAM = '8eqh8wppT9c5rw4ERqNCffvU6cNFJWff9WmkcYtmGiqC' +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function stubChain(): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: {}, + } as unknown as SolanaChain +} + +function generate(opts = {}) { + return new DeployTokenPool().generate(stubChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + authority: AUTHORITY, + ...opts, + }) +} + +describe('DeployTokenPool (cct/solana)', () => { + describe('generate', () => { + it('builds unsigned initialize pool instruction', async () => { + const unsigned = await generate() + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(unsigned.instructions.length, 1) + assert.equal(unsigned.instructions[0]!.programId.toBase58(), BURN_MINT_POOL_PROGRAM) + assert.equal( + unsigned.poolAddress, + deriveTokenPoolConfigPda( + new PublicKey(BURN_MINT_POOL_PROGRAM), + new PublicKey(TOKEN), + ).toBase58(), + ) + assert.equal( + unsigned.poolSignerAddress, + deriveTokenPoolSignerPda( + resolveTokenPoolProgram('burn-mint'), + new PublicKey(TOKEN), + ).toBase58(), + ) + }) + + it('adds configure allowlist instruction when provided', async () => { + const unsigned = await generate({ + allowlist: [Keypair.generate().publicKey.toBase58()], + }) + + assert.equal(unsigned.instructions.length, 2) + assert.equal(unsigned.instructions[1]!.programId.toBase58(), BURN_MINT_POOL_PROGRAM) + }) + + it('creates the pool signer ATA when requested', async () => { + const chain = Object.assign(stubChain(), { + connection: { getAccountInfo: async () => ({ owner: TOKEN_PROGRAM_ID }) }, + }) + const unsigned = await new DeployTokenPool().generate(chain, { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + authority: AUTHORITY, + createPoolSignerATA: true, + }) + const poolSigner = deriveTokenPoolSignerPda( + resolveTokenPoolProgram('burn-mint'), + new PublicKey(TOKEN), + ) + const ata = getAssociatedTokenAddressSync( + new PublicKey(TOKEN), + poolSigner, + true, + TOKEN_PROGRAM_ID, + ) + const createATA = unsigned.instructions[1]! + + assert.equal(unsigned.instructions.length, 2) + assert.equal(createATA.programId.toBase58(), ASSOCIATED_TOKEN_PROGRAM_ID.toBase58()) + assert.equal(createATA.data[0], 1) // CreateIdempotent + assert.equal(createATA.keys[1]!.pubkey.toBase58(), ata.toBase58()) + assert.equal(createATA.keys[2]!.pubkey.toBase58(), poolSigner.toBase58()) + }) + + it('uses canonical lock-release pool program', async () => { + const unsigned = await generate({ poolType: 'lock-release' }) + + assert.equal(unsigned.instructions[0]!.programId.toBase58(), LOCK_RELEASE_POOL_PROGRAM) + }) + + it('defaults authority to payer', async () => { + const unsigned = await generate({ authority: undefined }) + + assert.ok(unsigned.instructions[0]!.keys.some((key) => key.pubkey.toBase58() === PAYER)) + }) + }) + + describe('validation', () => { + it('rejects a non-array allowlist', async () => { + await assert.rejects( + () => generate({ allowlist: 'not-an-array' }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'allowlist', + ) + }) + + it('rejects a non-boolean createPoolSignerATA', async () => { + await assert.rejects( + () => generate({ createPoolSignerATA: 'yes' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'createPoolSignerATA', + ) + }) + + it('rejects invalid pool types', async () => { + await assert.rejects( + () => generate({ poolType: 'custom' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'deployTokenPool' && + err.context.param === 'poolType', + ) + }) + + it('rejects an empty authority', async () => { + await assert.rejects( + () => generate({ authority: '' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'deployTokenPool' && + err.context.param === 'authority', + ) + }) + + it('rejects duplicate allowlist addresses', async () => { + const address = Keypair.generate().publicKey.toBase58() + await assert.rejects( + () => generate({ allowlist: [address, address] }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'allowlist[1]', + ) + }) + + it('rejects invalid allowlist addresses', async () => { + await assert.rejects( + () => generate({ allowlist: ['not-a-pubkey'] }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'deployTokenPool' && + err.context.param === 'allowlist[0]', + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns pool addresses', async () => { + const result = await new DeployTokenPool().execute( + Object.assign(stubChain(), { + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }), + { + tokenAddress: TOKEN, + poolType: 'burn-mint', + wallet: { ...WALLET, publicKey: new PublicKey(PAYER) }, + }, + ) + + assert.equal(result.hash, HASH) + assert.ok(result.poolAddress) + assert.ok(result.poolSignerAddress) + }) + + it('rejects signed deploy when authority is not the wallet', async () => { + await assert.rejects( + () => + new DeployTokenPool().execute(stubChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + wallet: WALLET, + authority: AUTHORITY, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'deployTokenPool' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/deploy-token-pool.ts b/ccip-sdk/src/cct/solana/token-pool/operations/deploy-token-pool.ts new file mode 100644 index 000000000..2f735c3ee --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/deploy-token-pool.ts @@ -0,0 +1,224 @@ +import { type PublicKey, SystemProgram } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type TokenPoolType, + createTokenPoolProgram, + deriveTokenPoolConfigPda, + deriveTokenPoolGlobalConfigPda, + deriveTokenPoolProgramDataPda, + deriveTokenPoolSignerPda, + resolveTokenPoolProgram, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { CreateTokenAccount } from '../../token/operations/create-token-account.ts' +import { + parsePublicKey, + validateAuthorityMatchesWallet, + validatePoolType, + validateUniquePublicKeys, +} from '../../validate.ts' + +/** + * Parameters for initializing a Solana token pool, optionally with an allowlist. + * + * @remarks Targets only the canonical CCIP pool programs selected by `poolType` (`burn-mint`, + * `lock-release`). Deploying a custom pool program is intentionally unsupported because this + * operation initializes pools through the SDK's bundled program IDL; custom programs may use a + * different initialize instruction or pool-state PDA layout. This is a deploy-operation scope, + * not a protocol limitation: the registry and lookup-table operations remain program-agnostic. + */ +type DeployTokenPoolParams = { + /** Token mint address this pool manages. */ + tokenAddress: string + /** Canonical token pool program to deploy: BurnMint or LockRelease. */ + poolType: TokenPoolType + /** + * Addresses to seed into the pool allowlist during initialization. + * Providing any address also enables allowlist enforcement. + * If omitted, the pool is initialized without an allowlist. + */ + allowlist?: string[] + /** + * Create the pool signer PDA's associated token account (`pool_token_account`) idempotently. + * + * @remarks The pool requires a `pool_token_account` (the associated token account owned by the + * `pool_signer` PDA) to lock/release or mint on transfers. Without it, a `ccip-send` fails with + * `AccountNotInitialized (3012)`. Setting `createPoolSignerATA: true` creates this account in the + * deploy transaction; otherwise create it separately before any transfer. + * + * Defaults to false. + */ + createPoolSignerATA?: boolean + /** Pool authority. Defaults to payer for unsigned generation and wallet public key for execute. */ + authority?: string +} + +/** Parameters for unsigned Solana token pool deploy generation. */ +export type GenerateDeployTokenPoolParams = SolanaGenerateParams + +type ParsedDeployTokenPoolParams = { + tokenMint: PublicKey + poolProgram: PublicKey + payer: PublicKey + authority: PublicKey + allowlist: PublicKey[] + createPoolSignerATA: boolean +} + +/** Unsigned Solana token pool deploy result plus derived pool PDAs. */ +export type GenerateDeployTokenPoolResult = UnsignedSolanaTx & { + poolAddress: string + poolSignerAddress: string +} + +/** Parameters for executing Solana token pool deploy. */ +export type ExecuteDeployTokenPoolParams = SolanaExecuteParams + +/** Result of executing Solana token pool deploy plus derived pool PDAs. */ +export type ExecuteDeployTokenPoolResult = TransactionResult & { + poolAddress: string + poolSignerAddress: string +} + +/** + * Initializes a Solana token pool, optionally configuring an allowlist. + * @remarks The allowlist must not contain duplicate addresses. + */ +export class DeployTokenPool extends SolanaOperation< + DeployTokenPoolParams, + GenerateDeployTokenPoolResult, + ParsedDeployTokenPoolParams +> { + readonly name = 'deployTokenPool' + + /** Parses all public keys before any RPC. */ + protected override parse(params: GenerateDeployTokenPoolParams): ParsedDeployTokenPoolParams { + validatePoolType(this.name, 'poolType', params.poolType) + if (params.allowlist !== undefined && !Array.isArray(params.allowlist)) { + throw new CCTParamsInvalidError(this.name, 'allowlist', 'must be an array') + } + if ( + params.createPoolSignerATA !== undefined && + typeof params.createPoolSignerATA !== 'boolean' + ) { + throw new CCTParamsInvalidError(this.name, 'createPoolSignerATA', 'must be a boolean') + } + + const allowlist = (params.allowlist ?? []).map((address, i) => + parsePublicKey(this.name, `allowlist[${i}]`, address), + ) + validateUniquePublicKeys(this.name, 'allowlist', allowlist) + + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenMint: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + poolProgram: resolveTokenPoolProgram(params.poolType), + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + allowlist, + createPoolSignerATA: params.createPoolSignerATA ?? false, + } + } + + /** Builds the unsigned Solana token pool initialize instruction set. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedDeployTokenPoolParams, + ): Promise { + const { tokenMint, poolProgram, payer, authority, allowlist, createPoolSignerATA } = opts + const program = createTokenPoolProgram(chain, poolProgram, payer) + const state = deriveTokenPoolConfigPda(poolProgram, tokenMint) + const poolSigner = deriveTokenPoolSignerPda(poolProgram, tokenMint) + + const instructions = [ + await program.methods + .initialize() + .accountsStrict({ + state, + mint: tokenMint, + authority, + systemProgram: SystemProgram.programId, + program: poolProgram, + programData: deriveTokenPoolProgramDataPda(poolProgram), + config: deriveTokenPoolGlobalConfigPda(poolProgram), + }) + .instruction(), + ] + + if (createPoolSignerATA) { + // Append ATA creation after initialize (initialize must be main instruction index 0) + instructions.push( + ...( + await new CreateTokenAccount().generate(chain, { + payer: payer.toBase58(), + tokenAddress: tokenMint.toBase58(), + ownerAddress: poolSigner.toBase58(), + }) + ).instructions, + ) + } + + if (allowlist.length) { + instructions.push( + await program.methods + .configureAllowList(allowlist, true) + .accountsStrict({ + state, + mint: tokenMint, + authority, + systemProgram: SystemProgram.programId, + }) + .instruction(), + ) + } + + chain.logger.debug( + `${this.name}: token = ${tokenMint.toBase58()}, poolProgram = ${poolProgram.toBase58()}`, + ) + return { + family: ChainFamily.Solana, + instructions, + mainIndex: 0, + poolAddress: state.toBase58(), + poolSignerAddress: poolSigner.toBase58(), + } + } + + /** Generate, sign, simulate, send, and confirm with wallet.publicKey as payer. */ + override async execute( + chain: SolanaChain, + params: ExecuteDeployTokenPoolParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'deployTokenPool requires authority to be the executing wallet. Use generateUnsignedDeployTokenPool for vault-owned pools and have the vault sign/execute it.', + ) + } + + const tx = await this.buildUnsigned(chain, parsed) + const hash = await submit(chain, wallet, tx, this.name, computeUnits) + return { + ...hash, + poolAddress: tx.poolAddress, + poolSignerAddress: tx.poolSignerAddress, + } + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/edit-chain-remote-config.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/edit-chain-remote-config.test.ts new file mode 100644 index 000000000..312a69e18 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/edit-chain-remote-config.test.ts @@ -0,0 +1,190 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey, SystemProgram } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import { tokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { + deriveTokenPoolChainConfigPda, + deriveTokenPoolConfigPda, + resolveTokenPoolProgram, +} from '../../programs/token-pool.ts' +import { EditChainRemoteConfig } from './edit-chain-remote-config.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const SELECTOR = 5009297550715157269n +const REMOTE_TOKEN = '0x1234567890abcdef1234567890abcdef12345678' +const REMOTE_POOLS = ['0x1234567890abcdef1234567890abcdef12345678', '0xaabbccdd'] +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function chain(): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: {}, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(chain(), { + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts = {}) { + return new EditChainRemoteConfig().generate(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + authority: AUTHORITY, + remoteChainSelector: SELECTOR, + remoteTokenAddress: REMOTE_TOKEN, + remotePoolAddresses: REMOTE_POOLS, + remoteTokenDecimals: 18, + ...opts, + }) +} + +describe('EditChainRemoteConfig (cct/solana)', () => { + describe('generate', () => { + it('builds a padded remote-token config with remote pools', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + const poolProgram = resolveTokenPoolProgram('burn-mint') + const decoded = tokenPoolCoder.instruction.decode(instruction!.data) + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction!.programId.toBase58(), poolProgram.toBase58()) + assert.deepEqual( + instruction!.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveTokenPoolConfigPda(poolProgram, new PublicKey(TOKEN)).toBase58(), + isSigner: false, + isWritable: false, + }, + { + pubkey: deriveTokenPoolChainConfigPda( + poolProgram, + SELECTOR, + new PublicKey(TOKEN), + ).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: AUTHORITY, isSigner: true, isWritable: true }, + { pubkey: SystemProgram.programId.toBase58(), isSigner: false, isWritable: false }, + ], + ) + assert.ok(decoded) + assert.equal(decoded.name, 'editChainRemoteConfig') + const data = decoded.data as { + remoteChainSelector: { toString(): string } + cfg: { + tokenAddress: { address: Buffer } + poolAddresses: { address: Buffer }[] + decimals: number + } + } + assert.equal(data.remoteChainSelector.toString(), SELECTOR.toString()) + assert.deepEqual( + data.cfg.tokenAddress.address, + Buffer.from(REMOTE_TOKEN.slice(2).padStart(64, '0'), 'hex'), + ) + assert.deepEqual( + data.cfg.poolAddresses.map(({ address }) => address), + REMOTE_POOLS.map((address) => Buffer.from(address.slice(2), 'hex')), + ) + assert.equal(data.cfg.decimals, 18) + }) + + it('supports a zero remote-chain selector', async () => { + await assert.doesNotReject(() => generate({ remoteChainSelector: 0n })) + }) + + it('uses a compatible custom pool program', async () => { + const poolProgramAddress = Keypair.generate().publicKey.toBase58() + const unsigned = await generate({ poolType: undefined, poolProgramAddress }) + + assert.equal(unsigned.instructions[0]?.programId.toBase58(), poolProgramAddress) + }) + }) + + describe('validation', () => { + it('rejects invalid remote configuration values', async () => { + for (const [opts, param] of [ + [{ remoteChainSelector: 1 }, 'remoteChainSelector'], + [{ remoteChainSelector: -1n }, 'remoteChainSelector'], + [{ remoteChainSelector: 1n << 64n }, 'remoteChainSelector'], + [{ remoteTokenAddress: '0x123' }, 'remoteTokenAddress'], + [{ remotePoolAddresses: [''] }, 'remotePoolAddresses[0]'], + [{ remotePoolAddresses: ['0x123'] }, 'remotePoolAddresses[0]'], + [{ remotePoolAddresses: ['0x1234', '0x1234'] }, 'remotePoolAddresses[1]'], + [{ remotePoolAddresses: '0x12' }, 'remotePoolAddresses'], + [{ remoteTokenDecimals: 256 }, 'remoteTokenDecimals'], + ] as const) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new EditChainRemoteConfig().execute(submitChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + remoteChainSelector: SELECTOR, + remoteTokenAddress: REMOTE_TOKEN, + remotePoolAddresses: REMOTE_POOLS, + remoteTokenDecimals: 18, + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-wallet authority for signed editing', async () => { + await assert.rejects( + () => + new EditChainRemoteConfig().execute(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + authority: AUTHORITY, + remoteChainSelector: SELECTOR, + remoteTokenAddress: REMOTE_TOKEN, + remotePoolAddresses: REMOTE_POOLS, + remoteTokenDecimals: 18, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'editChainRemoteConfig' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/edit-chain-remote-config.ts b/ccip-sdk/src/cct/solana/token-pool/operations/edit-chain-remote-config.ts new file mode 100644 index 000000000..406a0b83d --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/edit-chain-remote-config.ts @@ -0,0 +1,187 @@ +import { Buffer } from 'buffer' + +import { type PublicKey, SystemProgram } from '@solana/web3.js' +import BN from 'bn.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type PoolProgramRef, + createTokenPoolProgram, + deriveTokenPoolChainConfigPda, + deriveTokenPoolConfigPda, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + U64_MAX, + parseHexBytes, + parseNonEmptyHexBytes, + parsePublicKey, + resolvePoolProgram, + validateAuthorityMatchesWallet, + validateBigInt, + validateInteger, + validateUniqueHexBytes, +} from '../../validate.ts' + +/** Parameters shared by Solana token pool remote-config editing generation and execution. */ +type EditChainRemoteConfigParams = PoolProgramRef & { + /** Token mint address managed by the local pool. */ + tokenAddress: string + /** CCIP selector of the remote chain (`u64`). */ + remoteChainSelector: bigint + /** Hex-encoded remote token address, optionally `0x`-prefixed, up to 32 bytes. Left-padded in the instruction. */ + remoteTokenAddress: string + /** + * Hex-encoded remote pool addresses, optionally `0x`-prefixed. Stored at native byte length; + * unlike `remoteTokenAddress`, they are not left-padded. + */ + remotePoolAddresses: string[] + /** Remote token decimals (`u8`): an integer from 0 to 255; 0 is valid. */ + remoteTokenDecimals: number + /** Pool owner. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type ParsedEditChainRemoteConfigParams = { + tokenAddress: PublicKey + poolProgram: PublicKey + payer: PublicKey + authority: PublicKey + remoteChainSelector: bigint + remoteTokenAddress: Buffer + remotePoolAddresses: Buffer[] + remoteTokenDecimals: number +} + +/** Parameters for unsigned Solana token pool remote configuration editing. */ +export type GenerateEditChainRemoteConfigParams = SolanaGenerateParams + +/** Unsigned Solana token pool remote configuration editing result. */ +export type GenerateEditChainRemoteConfigResult = UnsignedSolanaTx + +/** Parameters for executing Solana token pool remote configuration editing. */ +export type ExecuteEditChainRemoteConfigParams = SolanaExecuteParams + +/** Result of executing Solana token pool remote configuration editing. */ +export type ExecuteEditChainRemoteConfigResult = TransactionResult + +/** + * Replaces an initialized remote-chain config. + * + * @remarks + * Full replacement, not a partial update — pass the complete intended config for all three fields, + * or omitted values are cleared. For example, `remotePoolAddresses: []` clears all remote pools. + */ +export class EditChainRemoteConfig extends SolanaOperation< + EditChainRemoteConfigParams, + UnsignedSolanaTx, + ParsedEditChainRemoteConfigParams +> { + readonly name = 'editChainRemoteConfig' + + /** Parses config values and defaults authority to payer without mutating caller params. */ + protected override parse( + params: GenerateEditChainRemoteConfigParams, + ): ParsedEditChainRemoteConfigParams { + validateBigInt(this.name, 'remoteChainSelector', params.remoteChainSelector, 0n, U64_MAX) + validateInteger(this.name, 'remoteTokenDecimals', params.remoteTokenDecimals, 0, 255) + + const remoteTokenAddress = parseHexBytes( + this.name, + 'remoteTokenAddress', + params.remoteTokenAddress, + 32, + ) + + if (!Array.isArray(params.remotePoolAddresses)) { + throw new CCTParamsInvalidError(this.name, 'remotePoolAddresses', 'must be an array') + } + const remotePoolAddresses = params.remotePoolAddresses.map((address, i) => + parseNonEmptyHexBytes(this.name, `remotePoolAddresses[${i}]`, address), + ) + validateUniqueHexBytes( + this.name, + 'remotePoolAddresses', + remotePoolAddresses, + 'remote pool addresses', + ) + + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + poolProgram: resolvePoolProgram(this.name, params), + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + remoteChainSelector: params.remoteChainSelector, + remoteTokenAddress, + remotePoolAddresses, + remoteTokenDecimals: params.remoteTokenDecimals, + } + } + + /** Builds the unsigned Solana `editChainRemoteConfig` instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedEditChainRemoteConfigParams, + ): Promise { + const program = createTokenPoolProgram(chain, opts.poolProgram, opts.payer) + const state = deriveTokenPoolConfigPda(opts.poolProgram, opts.tokenAddress) + const chainConfig = deriveTokenPoolChainConfigPda( + opts.poolProgram, + opts.remoteChainSelector, + opts.tokenAddress, + ) + const paddedRemoteToken = Buffer.alloc(32) + opts.remoteTokenAddress.copy(paddedRemoteToken, 32 - opts.remoteTokenAddress.length) + + const instruction = await program.methods + .editChainRemoteConfig(new BN(opts.remoteChainSelector.toString()), opts.tokenAddress, { + tokenAddress: { address: paddedRemoteToken }, + poolAddresses: opts.remotePoolAddresses.map((address) => ({ address })), + decimals: opts.remoteTokenDecimals, + }) + .accountsStrict({ + state, + chainConfig, + authority: opts.authority, + systemProgram: SystemProgram.programId, + }) + .instruction() + + chain.logger.debug( + `${this.name}: token = ${opts.tokenAddress.toBase58()}, poolProgram = ${opts.poolProgram.toBase58()}, remoteChainSelector = ${opts.remoteChainSelector}`, + ) + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the pool owner wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteEditChainRemoteConfigParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'editChainRemoteConfig requires authority to be the executing wallet. Use generateUnsignedEditChainRemoteConfig for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/get-token-pool-remotes.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/get-token-pool-remotes.test.ts new file mode 100644 index 000000000..321f171f8 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/get-token-pool-remotes.test.ts @@ -0,0 +1,90 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { PublicKey } from '@solana/web3.js' + +import type { TokenPoolRemote } from '../../../../chain.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveTokenPoolConfigPda } from '../../programs/token-pool.ts' +import { GetTokenPoolRemotes } from './get-token-pool-remotes.ts' + +function key(byte: number): PublicKey { + return new PublicKey(Uint8Array.from({ length: 32 }, () => byte)) +} + +const REMOTES: Record = { + 'ethereum-mainnet': { + remoteToken: '0x1234', + remotePools: ['0x5678'], + inboundRateLimiterState: { tokens: 25n, capacity: 50n, rate: 5n }, + outboundRateLimiterState: null, + }, +} + +describe('GetTokenPoolRemotes (cct/solana)', () => { + const mint = key(2) + const program = key(3) + const selector = 5009297550715157269n + + function chain(): SolanaChain { + return { + getTokenPoolRemotes: async (state: string, remoteChainSelector?: bigint) => { + assert.equal(state, deriveTokenPoolConfigPda(program, mint).toBase58()) + assert.equal(remoteChainSelector, selector) + return REMOTES + }, + } as unknown as SolanaChain + } + + describe('query', () => { + it('delegates selected remote config decoding to the chain reader', async () => { + const remotes = await new GetTokenPoolRemotes().query(chain(), { + tokenAddress: mint.toBase58(), + poolProgramAddress: program.toBase58(), + remoteChainSelector: selector, + }) + + assert.equal(remotes, REMOTES) + }) + + it('omits the selector to read all remote configs', async () => { + const chainWithAll = { + getTokenPoolRemotes: async (_state: string, remoteChainSelector?: bigint) => { + assert.equal(remoteChainSelector, undefined) + return REMOTES + }, + } as unknown as SolanaChain + + const remotes = await new GetTokenPoolRemotes().query(chainWithAll, { + tokenAddress: mint.toBase58(), + poolProgramAddress: program.toBase58(), + }) + + assert.equal(remotes, REMOTES) + }) + }) + + describe('validation', () => { + it('validates the token address and optional remote selector before reading', async () => { + const cases: Array<[Partial<{ tokenAddress: string; remoteChainSelector: bigint }>, string]> = + [ + [{ tokenAddress: 'invalid' }, 'tokenAddress'], + [{ remoteChainSelector: -1n }, 'remoteChainSelector'], + [{ remoteChainSelector: 1 as never }, 'remoteChainSelector'], + ] + for (const [opts, param] of cases) { + await assert.rejects( + new GetTokenPoolRemotes().query(chain(), { + tokenAddress: mint.toBase58(), + poolProgramAddress: program.toBase58(), + remoteChainSelector: selector, + ...opts, + }), + (error: unknown) => + error instanceof CCTParamsInvalidError && error.context.param === param, + ) + } + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/get-token-pool-remotes.ts b/ccip-sdk/src/cct/solana/token-pool/operations/get-token-pool-remotes.ts new file mode 100644 index 000000000..4cef282f7 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/get-token-pool-remotes.ts @@ -0,0 +1,57 @@ +import type { PublicKey } from '@solana/web3.js' + +import type { TokenPoolRemote } from '../../../../chain.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { type PoolProgramRef, deriveTokenPoolConfigPda } from '../../programs/token-pool.ts' +import { SolanaQuery } from '../../query.ts' +import { U64_MAX, parsePublicKey, resolvePoolProgram, validateBigInt } from '../../validate.ts' + +/** Parameters for reading Solana token pool remote-chain configurations. */ +export type GetTokenPoolRemotesParams = PoolProgramRef & { + /** Token mint address managed by the local pool. */ + tokenAddress: string + /** Optional CCIP selector of the destination chain to read (`u64`). */ + remoteChainSelector?: bigint +} + +/** Remote-chain configurations keyed by network name. */ +export type GetTokenPoolRemotesResult = Record + +/** {@link GetTokenPoolRemotesParams} with its mint and pool program resolved to public keys. */ +type ParsedGetTokenPoolRemotesParams = GetTokenPoolRemotesParams & { + mint: PublicKey + programId: PublicKey +} + +/** Reads all, or one selected, remote-chain configurations of a Solana token pool. */ +export class GetTokenPoolRemotes extends SolanaQuery< + GetTokenPoolRemotesParams, + GetTokenPoolRemotesResult, + ParsedGetTokenPoolRemotesParams +> { + readonly name = 'getTokenPoolRemotes' + + /** + * Converts the mint and pool program, and validates the optional remote-chain selector. + * @throws {@link CCTParamsInvalidError} if a pool parameter or selector is invalid. + */ + protected prepare(params: GetTokenPoolRemotesParams): ParsedGetTokenPoolRemotesParams { + if (params.remoteChainSelector !== undefined) { + validateBigInt(this.name, 'remoteChainSelector', params.remoteChainSelector, 0n, U64_MAX) + } + return { + ...params, + mint: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + programId: resolvePoolProgram(this.name, params), + } + } + + /** Derives the pool state PDA and delegates remote config decoding to the shared chain reader. */ + protected read( + chain: SolanaChain, + { mint, programId, remoteChainSelector }: ParsedGetTokenPoolRemotesParams, + ): Promise { + const state = deriveTokenPoolConfigPda(programId, mint).toBase58() + return chain.getTokenPoolRemotes(state, remoteChainSelector) + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/get-token-pool-state.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/get-token-pool-state.test.ts new file mode 100644 index 000000000..eb5ca7b1a --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/get-token-pool-state.test.ts @@ -0,0 +1,170 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { BorshAccountsCoder } from '@coral-xyz/anchor' +import { PublicKey } from '@solana/web3.js' + +import { CCIPTokenPoolStateNotFoundError } from '../../../../errors/index.ts' +import { tokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTDataDecodeError } from '../../../errors.ts' +import { decodeTokenPoolState, deriveTokenPoolConfigPda } from '../../programs/token-pool.ts' +import { GetTokenPoolState } from './get-token-pool-state.ts' + +function key(byte: number): PublicKey { + return new PublicKey(Uint8Array.from({ length: 32 }, () => byte)) +} + +function stateData(mint: PublicKey): Buffer { + return Buffer.concat([ + BorshAccountsCoder.accountDiscriminator('State'), + Buffer.from([1]), + key(3).toBuffer(), + mint.toBuffer(), + Buffer.from([6]), + key(4).toBuffer(), + key(5).toBuffer(), + key(6).toBuffer(), + key(7).toBuffer(), + key(8).toBuffer(), + key(9).toBuffer(), + key(10).toBuffer(), + key(11).toBuffer(), + Buffer.from([1, 1]), + Buffer.from([2, 0, 0, 0]), + key(12).toBuffer(), + key(13).toBuffer(), + key(14).toBuffer(), + ]) +} + +describe('GetTokenPoolState (cct/solana)', () => { + describe('query', () => { + it('returns decoded state fields', async () => { + const mint = key(2) + const chain = { + connection: { getAccountInfo: async () => ({ owner: key(1), data: stateData(mint) }) }, + } as unknown as SolanaChain + + const getTokenPoolState = new GetTokenPoolState() + const lockRelease = await getTokenPoolState.query(chain, { + poolType: 'lock-release', + tokenAddress: mint.toBase58(), + }) + const burnMint = await getTokenPoolState.query(chain, { + poolType: 'burn-mint', + tokenAddress: mint.toBase58(), + }) + const customProgram = key(15).toBase58() + const custom = await getTokenPoolState.query(chain, { + poolProgramAddress: customProgram, + tokenAddress: mint.toBase58(), + }) + + assert.equal(lockRelease.version, 1) + assert.equal(lockRelease.config.mint, mint.toBase58()) + assert.equal(lockRelease.config.decimals, 6) + // the op resolves to the union; the facade's overloads are what narrow for callers + assert.ok('canAcceptLiquidity' in lockRelease.config) + assert.equal(lockRelease.config.canAcceptLiquidity, true) + assert.equal(lockRelease.config.listEnabled, true) + assert.deepEqual(lockRelease.config.allowList, [key(12).toBase58(), key(13).toBase58()]) + assert.equal(lockRelease.config.rmnRemote, key(14).toBase58()) + assert.ok(!('rebalancer' in burnMint.config)) + assert.ok(!('canAcceptLiquidity' in burnMint.config)) + assert.equal(custom.programId, customProgram) + assert.equal(custom.config.mint, mint.toBase58()) + }) + + it('wraps decode failures with pool context', async () => { + const mint = key(2).toBase58() + const poolProgram = key(15).toBase58() + const chain = { + connection: { getAccountInfo: async () => ({ owner: key(1), data: Buffer.alloc(8) }) }, + } as unknown as SolanaChain + + await assert.rejects( + new GetTokenPoolState().query(chain, { + tokenAddress: mint, + poolProgramAddress: poolProgram, + }), + (error: unknown) => { + assert.ok(error instanceof CCTDataDecodeError) + assert.equal( + error.context.account, + deriveTokenPoolConfigPda(new PublicKey(poolProgram), new PublicKey(mint)).toBase58(), + ) + assert.equal(error.context.mint, mint) + assert.equal(error.context.poolProgram, poolProgram) + assert.equal(error.context.accountOwner, key(1).toBase58()) + assert.ok(error.cause instanceof Error) + return true + }, + ) + }) + + it('wraps non-Error decode causes', (t) => { + t.mock.method(tokenPoolCoder.accounts, 'decode', () => { + throw 'invalid account data' + }) + + assert.throws( + () => + decodeTokenPoolState(Buffer.alloc(8), { + tokenPool: key(1).toBase58(), + mint: key(2).toBase58(), + poolProgram: key(3).toBase58(), + accountOwner: key(4).toBase58(), + }), + (error: unknown) => { + assert.ok(error instanceof CCTDataDecodeError) + assert.ok(error.cause instanceof Error) + assert.equal(error.cause.message, 'invalid account data') + return true + }, + ) + }) + + it('includes the mint and program in missing-state context', async () => { + const mint = key(2).toBase58() + const poolProgram = key(15).toBase58() + const chain = { + connection: { getAccountInfo: async () => null }, + } as unknown as SolanaChain + + await assert.rejects( + new GetTokenPoolState().query(chain, { + tokenAddress: mint, + poolProgramAddress: poolProgram, + }), + (error: unknown) => { + assert.ok(error instanceof CCIPTokenPoolStateNotFoundError) + assert.match(error.message, /^TokenPool State PDA not found at /) + assert.equal(error.context.mint, mint) + assert.equal(error.context.poolProgram, poolProgram) + return true + }, + ) + }) + }) + + describe('validation', () => { + it('requires exactly one pool program reference', async () => { + const getTokenPoolState = new GetTokenPoolState() + const tokenAddress = key(2).toBase58() + const poolProgramAddress = key(15).toBase58() + + await assert.rejects( + getTokenPoolState.query( + {} as SolanaChain, + { + tokenAddress, + poolType: 'burn-mint', + poolProgramAddress, + } as never, + ), + ) + await assert.rejects(getTokenPoolState.query({} as SolanaChain, { tokenAddress } as never)) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/get-token-pool-state.ts b/ccip-sdk/src/cct/solana/token-pool/operations/get-token-pool-state.ts new file mode 100644 index 000000000..b3a494981 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/get-token-pool-state.ts @@ -0,0 +1,163 @@ +import type { PublicKey } from '@solana/web3.js' + +import { CCIPTokenPoolStateNotFoundError } from '../../../../errors/index.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { + type PoolProgramRef, + type TokenPoolConfig, + decodeTokenPoolState, + deriveTokenPoolConfigPda, +} from '../../programs/token-pool.ts' +import { SolanaQuery } from '../../query.ts' +import { parsePublicKey, resolvePoolProgram } from '../../validate.ts' + +export type { + BurnMintPoolProgramRef, + CustomPoolProgramRef, + LockReleasePoolProgramRef, + PoolProgramRef, +} from '../../programs/token-pool.ts' + +/** Parameters for reading a Solana token pool state. */ +export type GetTokenPoolStateParams = PoolProgramRef & { + tokenAddress: string +} + +type BaseConfig = { + tokenProgram: string + mint: string + decimals: number + poolSigner: string + poolTokenAccount: string + owner: string + proposedOwner: string + rateLimitAdmin: string + routerOnrampAuthority: string + router: string + listEnabled: boolean + allowList: string[] + rmnRemote: string +} + +type GetTokenPoolStateResultBase = { + stateAddress: string + /** Resolved pool program address: canonical for `poolType`, supplied for `poolProgramAddress`. */ + programId: string + version: number +} + +/** State returned for a burn-mint or custom token pool program. */ +export type BaseGetTokenPoolStateResult = GetTokenPoolStateResultBase & { + config: BaseConfig +} + +/** State returned for a lock-release token pool program. */ +export type LockReleaseGetTokenPoolStateResult = GetTokenPoolStateResultBase & { + config: BaseConfig & { + rebalancer: string + canAcceptLiquidity: boolean + } +} + +/** + * State returned for a canonical or custom token pool program. + * + * Reads queried with `poolProgramAddress` use the base config shape and omit lock-release-only + * fields, even when the supplied address is the lock-release program. The + * {@link SolanaTokenManager.getTokenPoolState} overloads pick the arm per pool type, so callers + * only narrow this union when the program is not known statically. + */ +export type GetTokenPoolStateResult = + | BaseGetTokenPoolStateResult + | LockReleaseGetTokenPoolStateResult + +function serializeBaseConfig(config: TokenPoolConfig): BaseConfig { + return { + tokenProgram: config.tokenProgram.toBase58(), + mint: config.mint.toBase58(), + decimals: config.decimals, + poolSigner: config.poolSigner.toBase58(), + poolTokenAccount: config.poolTokenAccount.toBase58(), + owner: config.owner.toBase58(), + proposedOwner: config.proposedOwner.toBase58(), + rateLimitAdmin: config.rateLimitAdmin.toBase58(), + routerOnrampAuthority: config.routerOnrampAuthority.toBase58(), + router: config.router.toBase58(), + listEnabled: config.listEnabled, + allowList: config.allowList.map((address) => address.toBase58()), + rmnRemote: config.rmnRemote.toBase58(), + } +} + +/** {@link GetTokenPoolStateParams} with its mint and pool program resolved to public keys. */ +type ParsedGetTokenPoolStateParams = GetTokenPoolStateParams & { + mint: PublicKey + programId: PublicKey +} + +/** Reads the complete state of a Solana token pool. */ +export class GetTokenPoolState extends SolanaQuery< + GetTokenPoolStateParams, + GetTokenPoolStateResult, + ParsedGetTokenPoolStateParams +> { + readonly name = 'getTokenPoolState' + + /** + * Converts the mint and resolves the pool program. + * @throws {@link CCTParamsInvalidError} if `tokenAddress` is not a public key, or if the pool + * program is identified by neither or both of `poolType` / `poolProgramAddress` + */ + protected prepare(params: GetTokenPoolStateParams): ParsedGetTokenPoolStateParams { + return { + ...params, + mint: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + programId: resolvePoolProgram(this.name, params), + } + } + + /** Reads and serializes the token pool config account; the facade's overloads narrow the arm. */ + protected async read( + chain: SolanaChain, + params: ParsedGetTokenPoolStateParams, + ): Promise { + const { mint, programId } = params + const state = deriveTokenPoolConfigPda(programId, mint) + + const account = await chain.connection.getAccountInfo(state) + if (!account) { + throw new CCIPTokenPoolStateNotFoundError(state.toBase58(), { + context: { + mint: params.tokenAddress, + poolProgram: programId.toBase58(), + }, + }) + } + + const { version, config } = decodeTokenPoolState(account.data, { + tokenPool: state.toBase58(), + mint: params.tokenAddress, + poolProgram: programId.toBase58(), + accountOwner: account.owner.toBase58(), + }) + const result = { + stateAddress: state.toBase58(), + programId: programId.toBase58(), + version, + } + const baseConfig = serializeBaseConfig(config) + + if (params.poolType === 'lock-release') { + return { + ...result, + config: { + ...baseConfig, + rebalancer: config.rebalancer.toBase58(), + canAcceptLiquidity: config.canAcceptLiquidity, + }, + } + } + + return { ...result, config: baseConfig } + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/index.ts b/ccip-sdk/src/cct/solana/token-pool/operations/index.ts new file mode 100644 index 000000000..fa19e29e0 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/index.ts @@ -0,0 +1,19 @@ +export * from './accept-ownership.ts' +export * from './append-remote-pool-addresses.ts' +export * from './apply-chain-updates.ts' +export * from './configure-allowlist.ts' +export * from './create-token-multisig.ts' +export * from './deploy-token-pool.ts' +export * from './delete-chain-remote-config.ts' +export * from './edit-chain-remote-config.ts' +export * from './get-token-pool-remotes.ts' +export * from './get-token-pool-state.ts' +export * from './init-chain-remote-config.ts' +export * from './provide-liquidity.ts' +export * from './remove-from-allowlist.ts' +export * from './set-can-accept-liquidity.ts' +export * from './set-chain-rate-limit.ts' +export * from './set-rate-limit-admin.ts' +export * from './set-rebalancer.ts' +export * from './transfer-ownership.ts' +export * from './withdraw-liquidity.ts' diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/init-chain-remote-config.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/init-chain-remote-config.test.ts new file mode 100644 index 000000000..35de57cca --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/init-chain-remote-config.test.ts @@ -0,0 +1,178 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey, SystemProgram } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import { tokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { + deriveTokenPoolChainConfigPda, + deriveTokenPoolConfigPda, + resolveTokenPoolProgram, +} from '../../programs/token-pool.ts' +import { InitChainRemoteConfig } from './init-chain-remote-config.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const SELECTOR = 5009297550715157269n +const REMOTE_TOKEN = '0x1234567890abcdef1234567890abcdef12345678' +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function chain(): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: {}, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(chain(), { + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts = {}) { + return new InitChainRemoteConfig().generate(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + authority: AUTHORITY, + remoteChainSelector: SELECTOR, + remoteTokenAddress: REMOTE_TOKEN, + remoteTokenDecimals: 18, + ...opts, + }) +} + +describe('InitChainRemoteConfig (cct/solana)', () => { + describe('generate', () => { + it('builds a padded remote-token config with no remote pools', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + const poolProgram = resolveTokenPoolProgram('burn-mint') + const decoded = tokenPoolCoder.instruction.decode(instruction!.data) + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction!.programId.toBase58(), poolProgram.toBase58()) + assert.deepEqual( + instruction!.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveTokenPoolConfigPda(poolProgram, new PublicKey(TOKEN)).toBase58(), + isSigner: false, + isWritable: false, + }, + { + pubkey: deriveTokenPoolChainConfigPda( + poolProgram, + SELECTOR, + new PublicKey(TOKEN), + ).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: AUTHORITY, isSigner: true, isWritable: true }, + { pubkey: SystemProgram.programId.toBase58(), isSigner: false, isWritable: false }, + ], + ) + assert.ok(decoded) + assert.equal(decoded.name, 'initChainRemoteConfig') + const data = decoded.data as { + remoteChainSelector: { toString(): string } + cfg: { tokenAddress: { address: Buffer }; poolAddresses: unknown[]; decimals: number } + } + assert.equal(data.remoteChainSelector.toString(), SELECTOR.toString()) + assert.deepEqual( + data.cfg.tokenAddress.address, + Buffer.from(REMOTE_TOKEN.slice(2).padStart(64, '0'), 'hex'), + ) + assert.deepEqual(data.cfg.poolAddresses, []) + assert.equal(data.cfg.decimals, 18) + }) + + it('supports a zero remote-chain selector', async () => { + await assert.doesNotReject(() => generate({ remoteChainSelector: 0n })) + }) + + it('uses a compatible custom pool program', async () => { + const poolProgramAddress = Keypair.generate().publicKey.toBase58() + const unsigned = await generate({ poolType: undefined, poolProgramAddress }) + + assert.equal(unsigned.instructions[0]?.programId.toBase58(), poolProgramAddress) + }) + }) + + describe('validation', () => { + it('rejects invalid remote configuration values', async () => { + for (const [opts, param] of [ + [{ remoteChainSelector: 1 }, 'remoteChainSelector'], + [{ remoteChainSelector: -1n }, 'remoteChainSelector'], + [{ remoteChainSelector: 1n << 64n }, 'remoteChainSelector'], + [{ remoteTokenAddress: '' }, 'remoteTokenAddress'], + [{ remoteTokenAddress: '0x' }, 'remoteTokenAddress'], + [{ remoteTokenAddress: '0x123' }, 'remoteTokenAddress'], + [{ remoteTokenAddress: null }, 'remoteTokenAddress'], + [{ remoteTokenDecimals: 256 }, 'remoteTokenDecimals'], + ] as const) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new InitChainRemoteConfig().execute(submitChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + remoteChainSelector: SELECTOR, + remoteTokenAddress: REMOTE_TOKEN, + remoteTokenDecimals: 18, + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-wallet authority for signed initialization', async () => { + await assert.rejects( + () => + new InitChainRemoteConfig().execute(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + authority: AUTHORITY, + remoteChainSelector: SELECTOR, + remoteTokenAddress: REMOTE_TOKEN, + remoteTokenDecimals: 18, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'initChainRemoteConfig' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/init-chain-remote-config.ts b/ccip-sdk/src/cct/solana/token-pool/operations/init-chain-remote-config.ts new file mode 100644 index 000000000..03df8f043 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/init-chain-remote-config.ts @@ -0,0 +1,167 @@ +import { Buffer } from 'buffer' + +import { type PublicKey, SystemProgram } from '@solana/web3.js' +import BN from 'bn.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type PoolProgramRef, + createTokenPoolProgram, + deriveTokenPoolChainConfigPda, + deriveTokenPoolConfigPda, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + U64_MAX, + parseHexBytes, + parsePublicKey, + resolvePoolProgram, + validateAuthorityMatchesWallet, + validateBigInt, + validateInteger, +} from '../../validate.ts' + +/** Parameters shared by Solana token pool remote-config initialization generation and execution. */ +type InitChainRemoteConfigParams = PoolProgramRef & { + /** Token mint address managed by the local pool. */ + tokenAddress: string + /** CCIP selector of the remote chain (`u64`). */ + remoteChainSelector: bigint + /** Hex-encoded remote token address, optionally `0x`-prefixed, up to 32 bytes. Left-padded in the instruction. */ + remoteTokenAddress: string + /** Decimals of the remote token (`u8`), not the local mint: an integer from 0 to 255; 0 is valid. */ + remoteTokenDecimals: number + /** Pool owner. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type ParsedInitChainRemoteConfigParams = { + tokenAddress: PublicKey + poolProgram: PublicKey + payer: PublicKey + authority: PublicKey + remoteChainSelector: bigint + remoteTokenAddress: Buffer + remoteTokenDecimals: number +} + +/** Parameters for unsigned Solana token pool remote configuration initialization. */ +export type GenerateInitChainRemoteConfigParams = SolanaGenerateParams + +/** Unsigned Solana token pool remote configuration initialization result. */ +export type GenerateInitChainRemoteConfigResult = UnsignedSolanaTx + +/** Parameters for executing Solana token pool remote configuration initialization. */ +export type ExecuteInitChainRemoteConfigParams = SolanaExecuteParams + +/** Result of executing Solana token pool remote configuration initialization. */ +export type ExecuteInitChainRemoteConfigResult = TransactionResult + +/** + * Initializes a previously unconfigured remote-chain config. + * + * @remarks Fails if the chain config already exists. + */ +export class InitChainRemoteConfig extends SolanaOperation< + InitChainRemoteConfigParams, + UnsignedSolanaTx, + ParsedInitChainRemoteConfigParams +> { + readonly name = 'initChainRemoteConfig' + + /** Parses config values and defaults authority to payer without mutating caller params. */ + protected override parse( + params: GenerateInitChainRemoteConfigParams, + ): ParsedInitChainRemoteConfigParams { + validateBigInt(this.name, 'remoteChainSelector', params.remoteChainSelector, 0n, U64_MAX) + validateInteger(this.name, 'remoteTokenDecimals', params.remoteTokenDecimals, 0, 255) + + const remoteTokenAddress = parseHexBytes( + this.name, + 'remoteTokenAddress', + params.remoteTokenAddress, + 32, + ) + + if (!remoteTokenAddress.length) { + throw new CCTParamsInvalidError(this.name, 'remoteTokenAddress', 'must not be empty') + } + + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + poolProgram: resolvePoolProgram(this.name, params), + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + remoteChainSelector: params.remoteChainSelector, + remoteTokenAddress, + remoteTokenDecimals: params.remoteTokenDecimals, + } + } + + /** Builds the unsigned Solana `initChainRemoteConfig` instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedInitChainRemoteConfigParams, + ): Promise { + const program = createTokenPoolProgram(chain, opts.poolProgram, opts.payer) + const state = deriveTokenPoolConfigPda(opts.poolProgram, opts.tokenAddress) + const chainConfig = deriveTokenPoolChainConfigPda( + opts.poolProgram, + opts.remoteChainSelector, + opts.tokenAddress, + ) + const paddedRemoteToken = Buffer.alloc(32) + opts.remoteTokenAddress.copy(paddedRemoteToken, 32 - opts.remoteTokenAddress.length) + + const instruction = await program.methods + .initChainRemoteConfig(new BN(opts.remoteChainSelector.toString()), opts.tokenAddress, { + tokenAddress: { address: paddedRemoteToken }, + poolAddresses: [], + decimals: opts.remoteTokenDecimals, + }) + .accountsStrict({ + state, + chainConfig, + authority: opts.authority, + systemProgram: SystemProgram.programId, + }) + .instruction() + + chain.logger.debug( + `${this.name}: token = ${opts.tokenAddress.toBase58()}, poolProgram = ${opts.poolProgram.toBase58()}, remoteChainSelector = ${opts.remoteChainSelector}`, + ) + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the pool owner wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteInitChainRemoteConfigParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'initChainRemoteConfig requires authority to be the executing wallet. Use generateUnsignedInitChainRemoteConfig for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/provide-liquidity.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/provide-liquidity.test.ts new file mode 100644 index 000000000..c2bba6330 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/provide-liquidity.test.ts @@ -0,0 +1,311 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { BorshAccountsCoder } from '@coral-xyz/anchor' +import { AccountLayout, TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync } from '@solana/spl-token' +import { Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import { lockReleaseTokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError, CCTTxFailedError } from '../../../errors.ts' +import { + deriveTokenPoolConfigPda, + deriveTokenPoolSignerPda, + resolveTokenPoolProgram, +} from '../../programs/token-pool.ts' +import { ProvideLiquidity } from './provide-liquidity.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function tokenAccount(delegate?: PublicKey, delegatedAmount = 0n, amount = 1_000_000n) { + const data = Buffer.alloc(AccountLayout.span) + AccountLayout.encode( + { + mint: new PublicKey(TOKEN), + owner: new PublicKey(AUTHORITY), + amount, + delegateOption: delegate ? 1 : 0, + delegate: delegate ?? PublicKey.default, + state: 1, + isNativeOption: 0, + isNative: 0n, + delegatedAmount, + closeAuthorityOption: 0, + closeAuthority: PublicKey.default, + }, + data, + ) + return { owner: TOKEN_PROGRAM_ID, data } +} + +function poolState(poolProgram: PublicKey, rebalancer = new PublicKey(AUTHORITY), accepts = true) { + const mint = new PublicKey(TOKEN) + const poolSigner = deriveTokenPoolSignerPda(poolProgram, mint) + return Buffer.concat([ + BorshAccountsCoder.accountDiscriminator('State'), + Buffer.from([1]), + TOKEN_PROGRAM_ID.toBuffer(), + mint.toBuffer(), + Buffer.from([9]), + poolSigner.toBuffer(), + PublicKey.default.toBuffer(), + new PublicKey(AUTHORITY).toBuffer(), + PublicKey.default.toBuffer(), + PublicKey.default.toBuffer(), + PublicKey.default.toBuffer(), + PublicKey.default.toBuffer(), + rebalancer.toBuffer(), + Buffer.from([accepts ? 1 : 0, 0]), + Buffer.alloc(4), + PublicKey.default.toBuffer(), + ]) +} + +function chain( + poolProgram = resolveTokenPoolProgram('lock-release'), + rebalancer = new PublicKey(AUTHORITY), + acceptsLiquidity = true, + sourceBalance = 1_000_000n, + delegatedAmount = 1_000_000n, +): SolanaChain { + const poolSigner = deriveTokenPoolSignerPda(poolProgram, new PublicKey(TOKEN)) + const state = deriveTokenPoolConfigPda(poolProgram, new PublicKey(TOKEN)) + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + getAccountInfo: async (address: PublicKey) => + address.equals(state) + ? { owner: poolProgram, data: poolState(poolProgram, rebalancer, acceptsLiquidity) } + : tokenAccount(poolSigner, delegatedAmount, sourceBalance), + }, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + const poolSigner = deriveTokenPoolSignerPda( + resolveTokenPoolProgram('lock-release'), + new PublicKey(TOKEN), + ) + const poolProgram = resolveTokenPoolProgram('lock-release') + const state = deriveTokenPoolConfigPda(poolProgram, new PublicKey(TOKEN)) + return Object.assign(chain(), { + connection: { + getAccountInfo: async (address: PublicKey) => + address.equals(state) + ? { owner: poolProgram, data: poolState(poolProgram, WALLET.publicKey) } + : tokenAccount(poolSigner, 1_000_000n), + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts = {}) { + return new ProvideLiquidity().generate(chain(), { + tokenAddress: TOKEN, + poolType: 'lock-release', + payer: PAYER, + authority: AUTHORITY, + amount: 1_000_000n, + ...opts, + }) +} + +describe('ProvideLiquidity (cct/solana)', () => { + describe('generate', () => { + it('builds the lock-release pool liquidity instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + const poolProgram = resolveTokenPoolProgram('lock-release') + const token = new PublicKey(TOKEN) + const poolSigner = deriveTokenPoolSignerPda(poolProgram, token) + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction!.programId.toBase58(), poolProgram.toBase58()) + assert.deepEqual( + instruction!.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveTokenPoolConfigPda(poolProgram, token).toBase58(), + isSigner: false, + isWritable: false, + }, + { pubkey: TOKEN_PROGRAM_ID.toBase58(), isSigner: false, isWritable: false }, + { pubkey: TOKEN, isSigner: false, isWritable: true }, + { pubkey: poolSigner.toBase58(), isSigner: false, isWritable: false }, + { + pubkey: getAssociatedTokenAddressSync(token, poolSigner, true).toBase58(), + isSigner: false, + isWritable: true, + }, + { + pubkey: getAssociatedTokenAddressSync(token, new PublicKey(AUTHORITY), true).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: AUTHORITY, isSigner: true, isWritable: false }, + ], + ) + const decoded = lockReleaseTokenPoolCoder.instruction.decode(instruction!.data) + assert.ok(decoded) + assert.equal(decoded.name, 'provideLiquidity') + assert.equal( + (decoded.data as { amount: { toString(): string } }).amount.toString(), + '1000000', + ) + }) + + it('explains failed liquidity preflight checks', async () => { + for (const [pool, hint] of [ + [chain(resolveTokenPoolProgram('lock-release'), PublicKey.default), 'setRebalancer'], + [ + chain(resolveTokenPoolProgram('lock-release'), new PublicKey(AUTHORITY), false), + 'setCanAcceptLiquidity(true)', + ], + [ + chain(resolveTokenPoolProgram('lock-release'), new PublicKey(AUTHORITY), true, 0n), + 'mint or transfer tokens first', + ], + ] as const) { + await assert.rejects( + () => + new ProvideLiquidity().generate(pool, { + tokenAddress: TOKEN, + poolType: 'lock-release', + payer: PAYER, + authority: AUTHORITY, + amount: 1n, + }), + (error: unknown) => error instanceof CCTTxFailedError && error.message.includes(hint), + ) + } + }) + + it('defaults authority to payer', async () => { + const unsigned = await new ProvideLiquidity().generate( + chain(resolveTokenPoolProgram('lock-release'), new PublicKey(PAYER)), + { + tokenAddress: TOKEN, + poolType: 'lock-release', + payer: PAYER, + amount: 1_000_000n, + }, + ) + + assert.equal(unsigned.instructions[0]!.keys[6]!.pubkey.toBase58(), PAYER) + }) + + it('bundles approval before liquidity when requested', async () => { + const poolProgram = resolveTokenPoolProgram('lock-release') + const poolSigner = deriveTokenPoolSignerPda(poolProgram, new PublicKey(TOKEN)) + const unsigned = await new ProvideLiquidity().generate( + chain(poolProgram, new PublicKey(AUTHORITY), true, 1_000_000n, 0n), + { + payer: PAYER, + tokenAddress: TOKEN, + poolType: 'lock-release', + authority: AUTHORITY, + amount: 1_000_000n, + includeApproval: true, + }, + ) + + assert.equal(unsigned.instructions.length, 2) + assert.equal(unsigned.mainIndex, 1) + assert.equal(unsigned.instructions[0]!.keys[1]!.pubkey.toBase58(), poolSigner.toBase58()) + assert.equal(unsigned.instructions[0]!.data.readBigUInt64LE(1), 1_000_000n) + assert.equal( + lockReleaseTokenPoolCoder.instruction.decode(unsigned.instructions[1]!.data)?.name, + 'provideLiquidity', + ) + }) + + it('supports a compatible custom pool program', async () => { + const poolProgramAddress = Keypair.generate().publicKey.toBase58() + const unsigned = await new ProvideLiquidity().generate( + chain(new PublicKey(poolProgramAddress)), + { + tokenAddress: TOKEN, + poolProgramAddress, + payer: PAYER, + authority: AUTHORITY, + amount: 1_000_000n, + }, + ) + + assert.equal(unsigned.instructions[0]?.programId.toBase58(), poolProgramAddress) + }) + }) + + describe('validation', () => { + it('rejects invalid public keys, amounts, and burn-mint pools', async () => { + for (const [opts, param] of [ + [{ tokenAddress: 'invalid' }, 'tokenAddress'], + [{ authority: 'invalid' }, 'authority'], + [{ amount: 0n }, 'amount'], + [{ amount: 0x1_0000_0000_0000_0000n }, 'amount'], + [{ poolType: 'burn-mint' as const }, 'poolType'], + [ + { + poolType: undefined, + poolProgramAddress: resolveTokenPoolProgram('burn-mint').toBase58(), + }, + 'poolProgramAddress', + ], + ]) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new ProvideLiquidity().execute(submitChain(), { + tokenAddress: TOKEN, + poolType: 'lock-release', + amount: 1_000_000n, + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-wallet authority for signed liquidity provision', async () => { + await assert.rejects( + () => + new ProvideLiquidity().execute(chain(), { + tokenAddress: TOKEN, + poolType: 'lock-release', + amount: 1_000_000n, + authority: AUTHORITY, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'provideLiquidity' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/provide-liquidity.ts b/ccip-sdk/src/cct/solana/token-pool/operations/provide-liquidity.ts new file mode 100644 index 000000000..4f8557f3b --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/provide-liquidity.ts @@ -0,0 +1,205 @@ +import type { PublicKey } from '@solana/web3.js' +import BN from 'bn.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTTxFailedError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type CustomPoolProgramRef, + type LockReleasePoolProgramRef, + createLockReleaseTokenPoolProgram, + deriveTokenPoolConfigPda, + deriveTokenPoolSignerPda, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { ApproveToken } from '../../token/operations/approve-token.ts' +import { + U64_MAX, + parsePublicKey, + resolveExistingTokenAccount, + resolveLockReleasePoolProgram, + validateAuthorityMatchesWallet, + validateBigInt, + validateDelegation, + validatePoolLiquidityConfig, +} from '../../validate.ts' + +type PoolProgramRef = LockReleasePoolProgramRef | CustomPoolProgramRef + +type ProvideLiquidityParams = PoolProgramRef & { + /** Token mint address managed by the lock-release pool. */ + tokenAddress: string + /** Amount to deposit in base units. Must be a positive u64. */ + amount: bigint + /** Pool rebalancer whose ATA for `tokenAddress` must hold `amount`. Defaults to `payer`. */ + authority?: string + /** Add an SPL Token approval for the pool signer before providing liquidity in the same transaction. */ + includeApproval?: boolean +} + +type ParsedProvideLiquidityParams = { + tokenAddress: PublicKey + amount: bigint + poolProgram: PublicKey + payer: PublicKey + authority: PublicKey + includeApproval: boolean +} + +/** Parameters for unsigned Solana lock-release pool liquidity provision. */ +export type GenerateProvideLiquidityParams = SolanaGenerateParams + +/** Unsigned Solana lock-release pool liquidity provision result. */ +export type GenerateProvideLiquidityResult = UnsignedSolanaTx + +/** Parameters for providing Solana lock-release pool liquidity. */ +export type ExecuteProvideLiquidityParams = SolanaExecuteParams + +/** Result of providing Solana lock-release pool liquidity. */ +export type ExecuteProvideLiquidityResult = TransactionResult + +/** Deposits tokens from a rebalancer's associated token account into a lock-release pool. */ +export class ProvideLiquidity extends SolanaOperation< + ProvideLiquidityParams, + UnsignedSolanaTx, + ParsedProvideLiquidityParams +> { + readonly name = 'provideLiquidity' + + /** Parses public keys, validates amount, and defaults authority to payer without mutating caller params. */ + protected override parse(params: GenerateProvideLiquidityParams): ParsedProvideLiquidityParams { + validateBigInt(this.name, 'amount', params.amount, 1n, U64_MAX) + + const poolProgram = resolveLockReleasePoolProgram(this.name, params) + const payer = parsePublicKey(this.name, 'payer', params.payer) + + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + amount: params.amount, + poolProgram, + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + includeApproval: params.includeApproval ?? false, + } + } + + /** Builds the unsigned Solana `provideLiquidity` instruction for a lock-release pool. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedProvideLiquidityParams, + ): Promise { + // The caller must be the configured rebalancer and the pool must accept deposits. + await validatePoolLiquidityConfig( + this.name, + chain, + opts.poolProgram, + opts.tokenAddress, + opts.authority, + ) + + // The rebalancer's source ATA must exist. + const { + tokenAccount: remoteTokenAccount, + tokenProgram, + account: remoteTokenAccountInfo, + } = await resolveExistingTokenAccount(chain.connection, opts.tokenAddress, opts.authority) + const poolSigner = deriveTokenPoolSignerPda(opts.poolProgram, opts.tokenAddress) + + // Avoid an opaque SPL Token insufficient-funds failure. + if (remoteTokenAccountInfo.amount < opts.amount) + throw new CCTTxFailedError( + this.name, + `source token account ${remoteTokenAccount.toBase58()} has ${ + remoteTokenAccountInfo.amount + }, but ${opts.amount} is required; mint or transfer tokens first`, + ) + + // The pool signer transfers from the rebalancer ATA as its SPL Token delegate. + if (!opts.includeApproval) { + validateDelegation( + this.name, + remoteTokenAccount, + remoteTokenAccountInfo, + poolSigner, + opts.amount, + ) + } + + // The pool vault ATA must have been created during pool initialization. + const { tokenAccount: poolTokenAccount } = await resolveExistingTokenAccount( + chain.connection, + opts.tokenAddress, + poolSigner, + ) + + const provideLiquidityInstruction = await createLockReleaseTokenPoolProgram( + chain, + opts.poolProgram, + opts.payer, + ) + .methods.provideLiquidity(new BN(opts.amount.toString())) + .accountsStrict({ + state: deriveTokenPoolConfigPda(opts.poolProgram, opts.tokenAddress), + tokenProgram, + mint: opts.tokenAddress, + poolSigner, + poolTokenAccount, + remoteTokenAccount, + authority: opts.authority, + }) + .instruction() + + const approval = opts.includeApproval + ? await new ApproveToken().generate(chain, { + payer: opts.payer.toBase58(), + tokenAddress: opts.tokenAddress.toBase58(), + delegate: poolSigner.toBase58(), + amount: opts.amount, + authority: opts.authority.toBase58(), + }) + : undefined + + chain.logger.debug( + `${ + this.name + }: token = ${opts.tokenAddress.toBase58()}, poolProgram = ${opts.poolProgram.toBase58()}, amount = ${ + opts.amount + }`, + ) + + return { + family: ChainFamily.Solana, + instructions: [...(approval?.instructions ?? []), provideLiquidityInstruction], + mainIndex: approval ? 1 : 0, + } + } + + /** Generate, sign, simulate, send, and confirm with the rebalancer wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteProvideLiquidityParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'provideLiquidity requires authority to be the executing wallet. Use generateUnsignedProvideLiquidity for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/remove-from-allowlist.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/remove-from-allowlist.test.ts new file mode 100644 index 000000000..b0c9ef8a5 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/remove-from-allowlist.test.ts @@ -0,0 +1,190 @@ +import assert from 'node:assert/strict' +import { createHash } from 'node:crypto' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey, SystemProgram } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import { tokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveTokenPoolConfigPda, resolveTokenPoolProgram } from '../../programs/token-pool.ts' +import { RemoveFromAllowlist } from './remove-from-allowlist.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const ALLOWED = Keypair.generate().publicKey.toBase58() +const SECOND_ALLOWED = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function stubChain(): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: {}, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(stubChain(), { + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts = {}) { + return new RemoveFromAllowlist().generate(stubChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + authority: AUTHORITY, + remove: [ALLOWED], + ...opts, + }) +} + +describe('RemoveFromAllowlist (cct/solana)', () => { + describe('generate', () => { + it('builds an unsigned remove from allowlist instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + const poolProgram = resolveTokenPoolProgram('burn-mint') + + assert.ok(instruction) + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction.programId.toBase58(), poolProgram.toBase58()) + assert.equal( + instruction.data.subarray(0, 8).toString('hex'), + createHash('sha256').update('global:remove_from_allow_list').digest('hex').slice(0, 16), + ) + assert.deepEqual( + instruction.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveTokenPoolConfigPda(poolProgram, new PublicKey(TOKEN)).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: TOKEN, isSigner: false, isWritable: false }, + { pubkey: AUTHORITY, isSigner: true, isWritable: true }, + { pubkey: SystemProgram.programId.toBase58(), isSigner: false, isWritable: false }, + ], + ) + }) + + it('encodes multiple addresses', async () => { + const unsigned = await generate({ remove: [ALLOWED, SECOND_ALLOWED] }) + const decoded = tokenPoolCoder.instruction.decode(unsigned.instructions[0]!.data) + + assert.ok(decoded) + assert.equal(decoded.name, 'removeFromAllowList') + assert.deepEqual( + (decoded.data as { remove: PublicKey[] }).remove.map((address) => address.toBase58()), + [ALLOWED, SECOND_ALLOWED], + ) + }) + + it('uses a compatible custom pool program', async () => { + const poolProgramAddress = Keypair.generate().publicKey.toBase58() + const unsigned = await new RemoveFromAllowlist().generate(stubChain(), { + tokenAddress: TOKEN, + poolProgramAddress, + payer: PAYER, + authority: AUTHORITY, + remove: [ALLOWED], + }) + + assert.equal(unsigned.instructions[0]?.programId.toBase58(), poolProgramAddress) + }) + }) + + describe('validation', () => { + it('rejects invalid pool program references', async () => { + await assert.rejects( + () => generate({ poolType: 'custom' }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'removeFromAllowlist' && + err.context.param === 'poolType', + ) + }) + + it('rejects an empty or non-array removal list', async () => { + for (const remove of [[], 'not-an-array']) { + await assert.rejects( + () => generate({ remove }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'removeFromAllowlist' && + err.context.param === 'remove', + ) + } + }) + + it('rejects invalid removal addresses', async () => { + await assert.rejects( + () => generate({ remove: ['not-a-pubkey'] }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'removeFromAllowlist' && + err.context.param === 'remove[0]', + ) + }) + + it('rejects duplicate removal addresses', async () => { + await assert.rejects( + () => generate({ remove: [ALLOWED, ALLOWED] }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'removeFromAllowlist' && + err.context.param === 'remove[1]', + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new RemoveFromAllowlist().execute(submitChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + remove: [ALLOWED], + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-wallet authority for signed removal', async () => { + await assert.rejects( + () => + new RemoveFromAllowlist().execute(stubChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + authority: AUTHORITY, + remove: [ALLOWED], + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'removeFromAllowlist' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/remove-from-allowlist.ts b/ccip-sdk/src/cct/solana/token-pool/operations/remove-from-allowlist.ts new file mode 100644 index 000000000..bd2e01a8f --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/remove-from-allowlist.ts @@ -0,0 +1,139 @@ +import { type PublicKey, SystemProgram } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type PoolProgramRef, + createTokenPoolProgram, + deriveTokenPoolConfigPda, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + parsePublicKey, + resolvePoolProgram, + validateAuthorityMatchesWallet, + validateUniquePublicKeys, +} from '../../validate.ts' + +/** Parameters shared by Solana token pool `removeFromAllowlist` generation and execution. */ +type RemoveFromAllowlistParams = PoolProgramRef & { + /** Token mint address managed by the pool. */ + tokenAddress: string + /** + * Addresses to remove from the pool allowlist. Must be non-empty and contain no duplicates. + * Every address must currently be allowlisted; if any is absent, the program reverts the entire + * removal. + */ + remove: string[] + /** Pool owner. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type ParsedRemoveFromAllowlistParams = { + tokenAddress: PublicKey + poolProgram: PublicKey + remove: PublicKey[] + payer: PublicKey + authority: PublicKey +} + +/** Parameters for unsigned Solana token pool allowlist removal. */ +export type GenerateRemoveFromAllowlistParams = SolanaGenerateParams + +/** Unsigned Solana token pool allowlist removal result. */ +export type GenerateRemoveFromAllowlistResult = UnsignedSolanaTx + +/** Parameters for executing Solana token pool allowlist removal. */ +export type ExecuteRemoveFromAllowlistParams = SolanaExecuteParams + +/** Result of executing Solana token pool allowlist removal. */ +export type ExecuteRemoveFromAllowlistResult = TransactionResult + +/** + * Removes addresses from a Solana token pool allowlist. + * @remarks Removed addresses must not contain duplicates. + */ +export class RemoveFromAllowlist extends SolanaOperation< + RemoveFromAllowlistParams, + UnsignedSolanaTx, + ParsedRemoveFromAllowlistParams +> { + readonly name = 'removeFromAllowlist' + + /** Parses public keys and defaults authority to payer without mutating caller params. */ + protected override parse( + params: GenerateRemoveFromAllowlistParams, + ): ParsedRemoveFromAllowlistParams { + if (!Array.isArray(params.remove) || params.remove.length === 0) { + throw new CCTParamsInvalidError(this.name, 'remove', 'must be a non-empty array') + } + + const remove = params.remove.map((address, index) => + parsePublicKey(this.name, `remove[${index}]`, address), + ) + validateUniquePublicKeys(this.name, 'remove', remove) + + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + poolProgram: resolvePoolProgram(this.name, params), + remove, + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + } + } + + /** Builds the unsigned Solana `removeFromAllowList` instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedRemoveFromAllowlistParams, + ): Promise { + const program = createTokenPoolProgram(chain, opts.poolProgram, opts.payer) + const state = deriveTokenPoolConfigPda(opts.poolProgram, opts.tokenAddress) + + const instruction = await program.methods + .removeFromAllowList(opts.remove) + .accountsStrict({ + state, + mint: opts.tokenAddress, + authority: opts.authority, + systemProgram: SystemProgram.programId, + }) + .instruction() + + chain.logger.debug( + `${this.name}: token = ${opts.tokenAddress.toBase58()}, poolProgram = ${opts.poolProgram.toBase58()}`, + ) + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the pool owner wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteRemoveFromAllowlistParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'removeFromAllowlist requires authority to be the executing wallet. Use generateUnsignedRemoveFromAllowlist for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/set-can-accept-liquidity.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/set-can-accept-liquidity.test.ts new file mode 100644 index 000000000..8653cf1ee --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/set-can-accept-liquidity.test.ts @@ -0,0 +1,152 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import { lockReleaseTokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveTokenPoolConfigPda, resolveTokenPoolProgram } from '../../programs/token-pool.ts' +import { SetCanAcceptLiquidity } from './set-can-accept-liquidity.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const ALLOW = true +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function chain(): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: {}, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(chain(), { + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts = {}) { + return new SetCanAcceptLiquidity().generate(chain(), { + tokenAddress: TOKEN, + poolType: 'lock-release', + payer: PAYER, + authority: AUTHORITY, + allow: ALLOW, + ...opts, + }) +} + +describe('SetCanAcceptLiquidity (cct/solana)', () => { + describe('generate', () => { + it('builds the set-can-accept-liquidity instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + const poolProgram = resolveTokenPoolProgram('lock-release') + const decoded = lockReleaseTokenPoolCoder.instruction.decode(instruction!.data) + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction!.programId.toBase58(), poolProgram.toBase58()) + assert.deepEqual( + instruction!.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveTokenPoolConfigPda(poolProgram, new PublicKey(TOKEN)).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: TOKEN, isSigner: false, isWritable: false }, + { pubkey: AUTHORITY, isSigner: true, isWritable: false }, + ], + ) + assert.ok(decoded) + assert.equal(decoded.name, 'setCanAcceptLiquidity') + assert.equal((decoded.data as { allow: boolean }).allow, ALLOW) + }) + + it('defaults authority to payer', async () => { + const unsigned = await generate({ authority: undefined }) + + assert.equal(unsigned.instructions[0]!.keys[2]!.pubkey.toBase58(), PAYER) + }) + + it('supports a compatible custom pool program', async () => { + const poolProgramAddress = Keypair.generate().publicKey.toBase58() + const unsigned = await generate({ poolType: undefined, poolProgramAddress }) + + assert.equal(unsigned.instructions[0]?.programId.toBase58(), poolProgramAddress) + }) + }) + + describe('validation', () => { + it('rejects invalid public keys, non-boolean values, and burn-mint pools', async () => { + for (const [opts, param] of [ + [{ tokenAddress: 'invalid' }, 'tokenAddress'], + [{ allow: 'true' }, 'allow'], + [{ poolType: 'burn-mint' as const }, 'poolType'], + [ + { + poolType: undefined, + poolProgramAddress: resolveTokenPoolProgram('burn-mint').toBase58(), + }, + 'poolProgramAddress', + ], + ]) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new SetCanAcceptLiquidity().execute(submitChain(), { + tokenAddress: TOKEN, + poolType: 'lock-release', + allow: ALLOW, + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-wallet authority for signed configuration', async () => { + await assert.rejects( + () => + new SetCanAcceptLiquidity().execute(chain(), { + tokenAddress: TOKEN, + poolType: 'lock-release', + allow: ALLOW, + authority: AUTHORITY, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setCanAcceptLiquidity' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/set-can-accept-liquidity.ts b/ccip-sdk/src/cct/solana/token-pool/operations/set-can-accept-liquidity.ts new file mode 100644 index 000000000..0bbd6ac6f --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/set-can-accept-liquidity.ts @@ -0,0 +1,125 @@ +import type { PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type CustomPoolProgramRef, + type LockReleasePoolProgramRef, + createLockReleaseTokenPoolProgram, + deriveTokenPoolConfigPda, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + parsePublicKey, + resolveLockReleasePoolProgram, + validateAuthorityMatchesWallet, +} from '../../validate.ts' + +/** Parameters shared by Solana lock-release pool liquidity-acceptance generation and execution. */ +type SetCanAcceptLiquidityParams = (LockReleasePoolProgramRef | CustomPoolProgramRef) & { + /** Token mint address managed by the pool. */ + tokenAddress: string + /** Whether to enable liquidity provision and withdrawal. */ + allow: boolean + /** Pool owner. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type ParsedSetCanAcceptLiquidityParams = { + tokenAddress: PublicKey + poolProgram: PublicKey + allow: boolean + payer: PublicKey + authority: PublicKey +} + +/** Parameters for unsigned Solana lock-release pool liquidity-acceptance configuration. */ +export type GenerateSetCanAcceptLiquidityParams = SolanaGenerateParams + +/** Unsigned Solana lock-release pool liquidity-acceptance configuration result. */ +export type GenerateSetCanAcceptLiquidityResult = UnsignedSolanaTx + +/** Parameters for executing Solana lock-release pool liquidity-acceptance configuration. */ +export type ExecuteSetCanAcceptLiquidityParams = SolanaExecuteParams + +/** Result of executing Solana lock-release pool liquidity-acceptance configuration. */ +export type ExecuteSetCanAcceptLiquidityResult = TransactionResult + +/** Sets whether a Solana lock-release token pool accepts liquidity. */ +export class SetCanAcceptLiquidity extends SolanaOperation< + SetCanAcceptLiquidityParams, + UnsignedSolanaTx, + ParsedSetCanAcceptLiquidityParams +> { + readonly name = 'setCanAcceptLiquidity' + + /** Parses public keys, validates `allow`, and defaults authority to payer. */ + protected override parse( + params: GenerateSetCanAcceptLiquidityParams, + ): ParsedSetCanAcceptLiquidityParams { + if (typeof params.allow !== 'boolean') { + throw new CCTParamsInvalidError(this.name, 'allow', 'must be a boolean') + } + + const poolProgram = resolveLockReleasePoolProgram(this.name, params) + const payer = parsePublicKey(this.name, 'payer', params.payer) + + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + poolProgram, + allow: params.allow, + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + } + } + + /** Builds the unsigned Solana `setCanAcceptLiquidity` instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedSetCanAcceptLiquidityParams, + ): Promise { + const instruction = await createLockReleaseTokenPoolProgram(chain, opts.poolProgram, opts.payer) + .methods.setCanAcceptLiquidity(opts.allow) + .accountsStrict({ + state: deriveTokenPoolConfigPda(opts.poolProgram, opts.tokenAddress), + mint: opts.tokenAddress, + authority: opts.authority, + }) + .instruction() + + chain.logger.debug( + `${this.name}: token = ${opts.tokenAddress.toBase58()}, poolProgram = ${opts.poolProgram.toBase58()}`, + ) + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the pool owner wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteSetCanAcceptLiquidityParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'setCanAcceptLiquidity requires authority to be the executing wallet. Use generateUnsignedSetCanAcceptLiquidity for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/set-chain-rate-limit.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/set-chain-rate-limit.test.ts new file mode 100644 index 000000000..337f5a67b --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/set-chain-rate-limit.test.ts @@ -0,0 +1,227 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import { tokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { + deriveTokenPoolChainConfigPda, + deriveTokenPoolConfigPda, + resolveTokenPoolProgram, +} from '../../programs/token-pool.ts' +import { SetChainRateLimit } from './set-chain-rate-limit.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const SELECTOR = 5009297550715157269n +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function chain(): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: {}, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(chain(), { + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts = {}) { + return new SetChainRateLimit().generate(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + authority: AUTHORITY, + remoteChainSelector: SELECTOR, + inbound: { enabled: true, capacity: 100n, rate: 10n }, + outbound: { enabled: true, capacity: 200n, rate: 20n }, + ...opts, + }) +} + +describe('SetChainRateLimit (cct/solana)', () => { + describe('generate', () => { + it('builds the set-chain-rate-limit instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + const poolProgram = resolveTokenPoolProgram('burn-mint') + const decoded = tokenPoolCoder.instruction.decode(instruction!.data) + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction!.programId.toBase58(), poolProgram.toBase58()) + assert.deepEqual( + instruction!.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveTokenPoolConfigPda(poolProgram, new PublicKey(TOKEN)).toBase58(), + isSigner: false, + isWritable: false, + }, + { + pubkey: deriveTokenPoolChainConfigPda( + poolProgram, + SELECTOR, + new PublicKey(TOKEN), + ).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: AUTHORITY, isSigner: true, isWritable: true }, + ], + ) + assert.ok(decoded) + assert.equal(decoded.name, 'setChainRateLimit') + const data = decoded.data as { + remoteChainSelector: { toString(): string } + mint: PublicKey + inbound: { + enabled: boolean + capacity: { toString(): string } + rate: { toString(): string } + } + outbound: { + enabled: boolean + capacity: { toString(): string } + rate: { toString(): string } + } + } + assert.equal(data.remoteChainSelector.toString(), SELECTOR.toString()) + assert.equal(data.mint.toBase58(), TOKEN) + assert.equal(data.inbound.enabled, true) + assert.equal(data.inbound.capacity.toString(), '100') + assert.equal(data.inbound.rate.toString(), '10') + assert.equal(data.outbound.enabled, true) + assert.equal(data.outbound.capacity.toString(), '200') + assert.equal(data.outbound.rate.toString(), '20') + }) + + it('encodes each enabled and disabled direction combination', async () => { + for (const [inbound, outbound, expected] of [ + [{ enabled: false }, { enabled: false }, [false, '0', '0', false, '0', '0']], + [ + { enabled: false, capacity: 0n, rate: 0n }, + { enabled: true, capacity: 200n, rate: 20n }, + [false, '0', '0', true, '200', '20'], + ], + [ + { enabled: true, capacity: 100n, rate: 10n }, + { enabled: false }, + [true, '100', '10', false, '0', '0'], + ], + ] as const) { + const unsigned = await generate({ remoteChainSelector: 0n, inbound, outbound }) + const decoded = tokenPoolCoder.instruction.decode(unsigned.instructions[0]!.data) + assert.ok(decoded) + const data = decoded.data as { + remoteChainSelector: { toString(): string } + inbound: { + enabled: boolean + capacity: { toString(): string } + rate: { toString(): string } + } + outbound: { + enabled: boolean + capacity: { toString(): string } + rate: { toString(): string } + } + } + + assert.equal(data.remoteChainSelector.toString(), '0') + assert.deepEqual( + [ + data.inbound.enabled, + data.inbound.capacity.toString(), + data.inbound.rate.toString(), + data.outbound.enabled, + data.outbound.capacity.toString(), + data.outbound.rate.toString(), + ], + expected, + ) + } + }) + + it('uses a compatible custom pool program', async () => { + const poolProgramAddress = Keypair.generate().publicKey.toBase58() + const unsigned = await generate({ poolType: undefined, poolProgramAddress }) + + assert.equal(unsigned.instructions[0]?.programId.toBase58(), poolProgramAddress) + }) + }) + + describe('validation', () => { + it('rejects invalid rate-limit values', async () => { + for (const [opts, param] of [ + [{ remoteChainSelector: 1 }, 'remoteChainSelector'], + [{ inbound: { enabled: true, capacity: -1n, rate: 1n } }, 'inbound.capacity'], + [{ outbound: { enabled: true, capacity: 1n, rate: 1n << 64n } }, 'outbound.rate'], + [{ inbound: { enabled: true, capacity: 1n, rate: 2n } }, 'inbound.rate'], + [{ outbound: { enabled: false, capacity: 1n, rate: 0n } }, 'outbound'], + [{ inbound: { enabled: 'true', capacity: 1n, rate: 1n } }, 'inbound.enabled'], + ] as const) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new SetChainRateLimit().execute(submitChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + remoteChainSelector: SELECTOR, + inbound: { enabled: true, capacity: 100n, rate: 10n }, + outbound: { enabled: true, capacity: 200n, rate: 20n }, + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-wallet authority for signed configuration', async () => { + await assert.rejects( + () => + new SetChainRateLimit().execute(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + authority: AUTHORITY, + remoteChainSelector: SELECTOR, + inbound: { enabled: true, capacity: 100n, rate: 10n }, + outbound: { enabled: true, capacity: 200n, rate: 20n }, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setChainRateLimit' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/set-chain-rate-limit.ts b/ccip-sdk/src/cct/solana/token-pool/operations/set-chain-rate-limit.ts new file mode 100644 index 000000000..b0d4e7529 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/set-chain-rate-limit.ts @@ -0,0 +1,225 @@ +import type { PublicKey } from '@solana/web3.js' +import BN from 'bn.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type PoolProgramRef, + createTokenPoolProgram, + deriveTokenPoolChainConfigPda, + deriveTokenPoolConfigPda, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + U64_MAX, + parsePublicKey, + resolvePoolProgram, + validateAuthorityMatchesWallet, + validateBigInt, +} from '../../validate.ts' + +/** + * Configuration for one direction of a token pool rate limiter. + * + * @remarks For a mint with 6 decimals, pass `1_000_000n` to represent one token. + */ +export type RateLimitConfig = + | { + /** Whether this directional rate limit is enforced. */ + enabled: true + /** Maximum token amount in the bucket (`u64`), at least `rate`. */ + capacity: bigint + /** Token amount restored to the bucket per second (`u64`), no greater than `capacity`. */ + rate: bigint + } + | { + /** Whether this directional rate limit is enforced. */ + enabled: false + /** Must be zero when provided; defaults to zero. */ + capacity?: bigint + /** Must be zero when provided; defaults to zero. */ + rate?: bigint + } + +type ParsedRateLimitConfig = { + enabled: boolean + capacity: bigint + rate: bigint +} + +function parseRateLimitConfig( + operation: string, + direction: string, + config: unknown, +): ParsedRateLimitConfig { + if (typeof config !== 'object' || config === null) { + throw new CCTParamsInvalidError(operation, direction, 'must be a rate-limit configuration') + } + + const { + enabled, + capacity: inputCapacity, + rate: inputRate, + } = config as Partial + + if (typeof enabled !== 'boolean') { + throw new CCTParamsInvalidError(operation, `${direction}.enabled`, 'must be a boolean') + } + + const capacity = !enabled && inputCapacity === undefined ? 0n : inputCapacity + const rate = !enabled && inputRate === undefined ? 0n : inputRate + + validateBigInt(operation, `${direction}.capacity`, capacity, 0n, U64_MAX) + validateBigInt(operation, `${direction}.rate`, rate, 0n, U64_MAX) + + if (enabled && rate > capacity) { + throw new CCTParamsInvalidError( + operation, + `${direction}.rate`, + 'must not exceed capacity when enabled', + ) + } + if (!enabled && (capacity !== 0n || rate !== 0n)) { + throw new CCTParamsInvalidError( + operation, + direction, + 'must have zero capacity and rate when disabled', + ) + } + return { enabled, capacity, rate } +} + +/** Parameters shared by Solana token pool rate-limit generation and execution. */ +type SetChainRateLimitParams = PoolProgramRef & { + /** Token mint address managed by the local pool. */ + tokenAddress: string + /** CCIP selector of the remote chain (`u64`). */ + remoteChainSelector: bigint + /** Rate limit for tokens received from the remote chain. Disabled limits default omitted values to zero. */ + inbound: RateLimitConfig + /** Rate limit for tokens sent to the remote chain. Disabled limits default omitted values to zero. */ + outbound: RateLimitConfig + /** Pool owner or rate-limit admin. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type ParsedSetChainRateLimitParams = { + tokenAddress: PublicKey + poolProgram: PublicKey + payer: PublicKey + authority: PublicKey + remoteChainSelector: bigint + inbound: ParsedRateLimitConfig + outbound: ParsedRateLimitConfig +} + +/** Parameters for unsigned Solana token pool rate-limit configuration. */ +export type GenerateSetChainRateLimitParams = SolanaGenerateParams + +/** Unsigned Solana token pool rate-limit configuration result. */ +export type GenerateSetChainRateLimitResult = UnsignedSolanaTx + +/** Parameters for executing Solana token pool rate-limit configuration. */ +export type ExecuteSetChainRateLimitParams = SolanaExecuteParams + +/** Result of executing Solana token pool rate-limit configuration. */ +export type ExecuteSetChainRateLimitResult = TransactionResult + +/** + * Sets inbound and outbound rate limits for an initialized remote-chain config. + * + * @remarks `authority` must be the pool owner or rate-limit admin. The remote-chain config must + * already exist. + */ +export class SetChainRateLimit extends SolanaOperation< + SetChainRateLimitParams, + UnsignedSolanaTx, + ParsedSetChainRateLimitParams +> { + readonly name = 'setChainRateLimit' + + /** Parses rate limits and defaults authority to payer without mutating caller params. */ + protected override parse(params: GenerateSetChainRateLimitParams): ParsedSetChainRateLimitParams { + validateBigInt(this.name, 'remoteChainSelector', params.remoteChainSelector, 0n, U64_MAX) + const inbound = parseRateLimitConfig(this.name, 'inbound', params.inbound) + const outbound = parseRateLimitConfig(this.name, 'outbound', params.outbound) + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + poolProgram: resolvePoolProgram(this.name, params), + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + remoteChainSelector: params.remoteChainSelector, + inbound, + outbound, + } + } + + /** Builds the unsigned Solana `setChainRateLimit` instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedSetChainRateLimitParams, + ): Promise { + const program = createTokenPoolProgram(chain, opts.poolProgram, opts.payer) + const instruction = await program.methods + .setChainRateLimit( + new BN(opts.remoteChainSelector.toString()), + opts.tokenAddress, + { + ...opts.inbound, + capacity: new BN(opts.inbound.capacity.toString()), + rate: new BN(opts.inbound.rate.toString()), + }, + { + ...opts.outbound, + capacity: new BN(opts.outbound.capacity.toString()), + rate: new BN(opts.outbound.rate.toString()), + }, + ) + .accountsStrict({ + state: deriveTokenPoolConfigPda(opts.poolProgram, opts.tokenAddress), + chainConfig: deriveTokenPoolChainConfigPda( + opts.poolProgram, + opts.remoteChainSelector, + opts.tokenAddress, + ), + authority: opts.authority, + }) + .instruction() + + chain.logger.debug( + `${this.name}: token = ${opts.tokenAddress.toBase58()}, poolProgram = ${opts.poolProgram.toBase58()}, remoteChainSelector = ${opts.remoteChainSelector}`, + ) + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the pool owner or rate-limit admin wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteSetChainRateLimitParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'setChainRateLimit requires authority to be the executing wallet. Use generateUnsignedSetChainRateLimit for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/set-rate-limit-admin.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/set-rate-limit-admin.test.ts new file mode 100644 index 000000000..526e7a5cc --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/set-rate-limit-admin.test.ts @@ -0,0 +1,145 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import { tokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveTokenPoolConfigPda, resolveTokenPoolProgram } from '../../programs/token-pool.ts' +import { SetRateLimitAdmin } from './set-rate-limit-admin.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const NEW_RATE_LIMIT_ADMIN = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function chain(): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: {}, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(chain(), { + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts = {}) { + return new SetRateLimitAdmin().generate(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + authority: AUTHORITY, + newRateLimitAdmin: NEW_RATE_LIMIT_ADMIN, + ...opts, + }) +} + +describe('SetRateLimitAdmin (cct/solana)', () => { + describe('generate', () => { + it('builds the set-rate-limit-admin instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + const poolProgram = resolveTokenPoolProgram('burn-mint') + const decoded = tokenPoolCoder.instruction.decode(instruction!.data) + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction!.programId.toBase58(), poolProgram.toBase58()) + assert.deepEqual( + instruction!.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveTokenPoolConfigPda(poolProgram, new PublicKey(TOKEN)).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: AUTHORITY, isSigner: true, isWritable: true }, + ], + ) + assert.ok(decoded) + assert.equal(decoded.name, 'setRateLimitAdmin') + const data = decoded.data as { mint: PublicKey; newRateLimitAdmin: PublicKey } + assert.equal(data.mint.toBase58(), TOKEN) + assert.equal(data.newRateLimitAdmin.toBase58(), NEW_RATE_LIMIT_ADMIN) + }) + + it('defaults authority to payer', async () => { + const unsigned = await generate({ authority: undefined }) + + assert.equal(unsigned.instructions[0]!.keys[1]!.pubkey.toBase58(), PAYER) + }) + + it('supports a compatible custom pool program', async () => { + const poolProgramAddress = Keypair.generate().publicKey.toBase58() + const unsigned = await generate({ poolType: undefined, poolProgramAddress }) + + assert.equal(unsigned.instructions[0]?.programId.toBase58(), poolProgramAddress) + }) + }) + + describe('validation', () => { + it('rejects invalid public keys', async () => { + for (const [opts, param] of [ + [{ tokenAddress: 'invalid' }, 'tokenAddress'], + [{ newRateLimitAdmin: 'invalid' }, 'newRateLimitAdmin'], + ]) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new SetRateLimitAdmin().execute(submitChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + newRateLimitAdmin: NEW_RATE_LIMIT_ADMIN, + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-wallet authority for signed configuration', async () => { + await assert.rejects( + () => + new SetRateLimitAdmin().execute(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + newRateLimitAdmin: NEW_RATE_LIMIT_ADMIN, + authority: AUTHORITY, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setRateLimitAdmin' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/set-rate-limit-admin.ts b/ccip-sdk/src/cct/solana/token-pool/operations/set-rate-limit-admin.ts new file mode 100644 index 000000000..c9e7ecdf4 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/set-rate-limit-admin.ts @@ -0,0 +1,115 @@ +import type { PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type PoolProgramRef, + createTokenPoolProgram, + deriveTokenPoolConfigPda, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + parsePublicKey, + resolvePoolProgram, + validateAuthorityMatchesWallet, +} from '../../validate.ts' + +/** Parameters shared by Solana token pool rate-limit admin generation and execution. */ +type SetRateLimitAdminParams = PoolProgramRef & { + /** Token mint address managed by the pool. */ + tokenAddress: string + /** Address authorized to configure the pool's chain rate limits. */ + newRateLimitAdmin: string + /** Pool owner. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type ParsedSetRateLimitAdminParams = { + tokenAddress: PublicKey + newRateLimitAdmin: PublicKey + poolProgram: PublicKey + payer: PublicKey + authority: PublicKey +} + +/** Parameters for unsigned Solana token pool rate-limit admin configuration. */ +export type GenerateSetRateLimitAdminParams = SolanaGenerateParams + +/** Unsigned Solana token pool rate-limit admin configuration result. */ +export type GenerateSetRateLimitAdminResult = UnsignedSolanaTx + +/** Parameters for executing Solana token pool rate-limit admin configuration. */ +export type ExecuteSetRateLimitAdminParams = SolanaExecuteParams + +/** Result of executing Solana token pool rate-limit admin configuration. */ +export type ExecuteSetRateLimitAdminResult = TransactionResult + +/** Sets the administrator authorized to configure a Solana token pool's chain rate limits. */ +export class SetRateLimitAdmin extends SolanaOperation< + SetRateLimitAdminParams, + UnsignedSolanaTx, + ParsedSetRateLimitAdminParams +> { + readonly name = 'setRateLimitAdmin' + + /** Parses public keys and defaults authority to payer without mutating caller params. */ + protected override parse(params: GenerateSetRateLimitAdminParams): ParsedSetRateLimitAdminParams { + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + newRateLimitAdmin: parsePublicKey(this.name, 'newRateLimitAdmin', params.newRateLimitAdmin), + poolProgram: resolvePoolProgram(this.name, params), + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + } + } + + /** Builds the unsigned Solana `setRateLimitAdmin` instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedSetRateLimitAdminParams, + ): Promise { + const program = createTokenPoolProgram(chain, opts.poolProgram, opts.payer) + const instruction = await program.methods + .setRateLimitAdmin(opts.tokenAddress, opts.newRateLimitAdmin) + .accountsStrict({ + state: deriveTokenPoolConfigPda(opts.poolProgram, opts.tokenAddress), + authority: opts.authority, + }) + .instruction() + + chain.logger.debug( + `${this.name}: token = ${opts.tokenAddress.toBase58()}, poolProgram = ${opts.poolProgram.toBase58()}`, + ) + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the pool owner wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteSetRateLimitAdminParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'setRateLimitAdmin requires authority to be the executing wallet. Use generateUnsignedSetRateLimitAdmin for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/set-rebalancer.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/set-rebalancer.test.ts new file mode 100644 index 000000000..140cc2ac7 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/set-rebalancer.test.ts @@ -0,0 +1,155 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import { lockReleaseTokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveTokenPoolConfigPda, resolveTokenPoolProgram } from '../../programs/token-pool.ts' +import { SetRebalancer } from './set-rebalancer.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const REBALANCER = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function chain(): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: {}, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(chain(), { + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts = {}) { + return new SetRebalancer().generate(chain(), { + tokenAddress: TOKEN, + poolType: 'lock-release', + payer: PAYER, + authority: AUTHORITY, + rebalancer: REBALANCER, + ...opts, + }) +} + +describe('SetRebalancer (cct/solana)', () => { + describe('generate', () => { + it('builds the set-rebalancer instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + const poolProgram = resolveTokenPoolProgram('lock-release') + const decoded = lockReleaseTokenPoolCoder.instruction.decode(instruction!.data) + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction!.programId.toBase58(), poolProgram.toBase58()) + assert.deepEqual( + instruction!.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveTokenPoolConfigPda(poolProgram, new PublicKey(TOKEN)).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: TOKEN, isSigner: false, isWritable: false }, + { pubkey: AUTHORITY, isSigner: true, isWritable: false }, + ], + ) + assert.ok(decoded) + assert.equal(decoded.name, 'setRebalancer') + assert.equal((decoded.data as { rebalancer: PublicKey }).rebalancer.toBase58(), REBALANCER) + }) + + it('defaults authority to payer and accepts the default rebalancer', async () => { + const unsigned = await generate({ + authority: undefined, + rebalancer: PublicKey.default.toBase58(), + }) + + assert.equal(unsigned.instructions[0]!.keys[2]!.pubkey.toBase58(), PAYER) + }) + + it('supports a compatible custom pool program', async () => { + const poolProgramAddress = Keypair.generate().publicKey.toBase58() + const unsigned = await generate({ poolType: undefined, poolProgramAddress }) + + assert.equal(unsigned.instructions[0]?.programId.toBase58(), poolProgramAddress) + }) + }) + + describe('validation', () => { + it('rejects invalid public keys and burn-mint pools', async () => { + for (const [opts, param] of [ + [{ tokenAddress: 'invalid' }, 'tokenAddress'], + [{ rebalancer: 'invalid' }, 'rebalancer'], + [{ poolType: 'burn-mint' as const }, 'poolType'], + [ + { + poolType: undefined, + poolProgramAddress: resolveTokenPoolProgram('burn-mint').toBase58(), + }, + 'poolProgramAddress', + ], + ]) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new SetRebalancer().execute(submitChain(), { + tokenAddress: TOKEN, + poolType: 'lock-release', + rebalancer: REBALANCER, + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-wallet authority for signed configuration', async () => { + await assert.rejects( + () => + new SetRebalancer().execute(chain(), { + tokenAddress: TOKEN, + poolType: 'lock-release', + rebalancer: REBALANCER, + authority: AUTHORITY, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setRebalancer' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/set-rebalancer.ts b/ccip-sdk/src/cct/solana/token-pool/operations/set-rebalancer.ts new file mode 100644 index 000000000..d7a8640ff --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/set-rebalancer.ts @@ -0,0 +1,118 @@ +import type { PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type CustomPoolProgramRef, + type LockReleasePoolProgramRef, + createLockReleaseTokenPoolProgram, + deriveTokenPoolConfigPda, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + parsePublicKey, + resolveLockReleasePoolProgram, + validateAuthorityMatchesWallet, +} from '../../validate.ts' + +/** Parameters shared by Solana lock-release pool rebalancer generation and execution. */ +type SetRebalancerParams = (LockReleasePoolProgramRef | CustomPoolProgramRef) & { + /** Token mint address managed by the pool. */ + tokenAddress: string + /** Address authorized to provide or withdraw pool liquidity (stored on the pool; not a transaction signer). Use the default/zero address (`11111111111111111111111111111111`) to disable rebalancing. */ + rebalancer: string + /** Pool owner. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type ParsedSetRebalancerParams = { + tokenAddress: PublicKey + rebalancer: PublicKey + poolProgram: PublicKey + payer: PublicKey + authority: PublicKey +} + +/** Parameters for unsigned Solana lock-release pool rebalancer configuration. */ +export type GenerateSetRebalancerParams = SolanaGenerateParams + +/** Unsigned Solana lock-release pool rebalancer configuration result. */ +export type GenerateSetRebalancerResult = UnsignedSolanaTx + +/** Parameters for executing Solana lock-release pool rebalancer configuration. */ +export type ExecuteSetRebalancerParams = SolanaExecuteParams + +/** Result of executing Solana lock-release pool rebalancer configuration. */ +export type ExecuteSetRebalancerResult = TransactionResult + +/** Sets the address authorized to provide or withdraw a Solana lock-release token pool's liquidity. */ +export class SetRebalancer extends SolanaOperation< + SetRebalancerParams, + UnsignedSolanaTx, + ParsedSetRebalancerParams +> { + readonly name = 'setRebalancer' + + /** Parses public keys and defaults authority to payer without mutating caller params. */ + protected override parse(params: GenerateSetRebalancerParams): ParsedSetRebalancerParams { + const poolProgram = resolveLockReleasePoolProgram(this.name, params) + const payer = parsePublicKey(this.name, 'payer', params.payer) + + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + rebalancer: parsePublicKey(this.name, 'rebalancer', params.rebalancer), + poolProgram, + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + } + } + + /** Builds the unsigned Solana `setRebalancer` instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedSetRebalancerParams, + ): Promise { + const instruction = await createLockReleaseTokenPoolProgram(chain, opts.poolProgram, opts.payer) + .methods.setRebalancer(opts.rebalancer) + .accountsStrict({ + state: deriveTokenPoolConfigPda(opts.poolProgram, opts.tokenAddress), + mint: opts.tokenAddress, + authority: opts.authority, + }) + .instruction() + + chain.logger.debug( + `${this.name}: token = ${opts.tokenAddress.toBase58()}, poolProgram = ${opts.poolProgram.toBase58()}`, + ) + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the pool owner wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteSetRebalancerParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'setRebalancer requires authority to be the executing wallet. Use generateUnsignedSetRebalancer for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/transfer-ownership.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/transfer-ownership.test.ts new file mode 100644 index 000000000..b9177b928 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/transfer-ownership.test.ts @@ -0,0 +1,186 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { BorshAccountsCoder } from '@coral-xyz/anchor' +import { Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import { tokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { deriveTokenPoolConfigPda, resolveTokenPoolProgram } from '../../programs/token-pool.ts' +import { TransferOwnership } from './transfer-ownership.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const NEW_OWNER = Keypair.generate().publicKey.toBase58() +const OWNER = Keypair.generate().publicKey +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function stateData(owner = OWNER): Buffer { + const key = PublicKey.default.toBuffer() + return Buffer.concat([ + BorshAccountsCoder.accountDiscriminator('State'), + Buffer.from([1]), + key, + new PublicKey(TOKEN).toBuffer(), + Buffer.from([6]), + key, + key, + owner.toBuffer(), + key, + key, + key, + key, + key, + Buffer.from([0, 0]), + Buffer.alloc(4), + key, + ]) +} + +function chain(owner = OWNER): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + getAccountInfo: async () => ({ owner: PublicKey.default, data: stateData(owner) }), + }, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(chain(), { + connection: { + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + getAccountInfo: async () => ({ owner: PublicKey.default, data: stateData() }), + }, + }) +} + +function generate(opts = {}) { + return new TransferOwnership().generate(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + payer: PAYER, + authority: AUTHORITY, + newOwner: NEW_OWNER, + ...opts, + }) +} + +describe('TransferOwnership (cct/solana)', () => { + describe('generate', () => { + it('builds the ownership-transfer instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + const poolProgram = resolveTokenPoolProgram('burn-mint') + const decoded = tokenPoolCoder.instruction.decode(instruction!.data) + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction!.programId.toBase58(), poolProgram.toBase58()) + assert.deepEqual( + instruction!.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveTokenPoolConfigPda(poolProgram, new PublicKey(TOKEN)).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: TOKEN, isSigner: false, isWritable: false }, + { pubkey: AUTHORITY, isSigner: true, isWritable: false }, + ], + ) + assert.ok(decoded) + assert.equal(decoded.name, 'transferOwnership') + assert.equal( + (decoded.data as { proposedOwner: PublicKey }).proposedOwner.toBase58(), + NEW_OWNER, + ) + }) + + it('defaults authority to payer', async () => { + const unsigned = await generate({ authority: undefined }) + + assert.equal(unsigned.instructions[0]!.keys[2]!.pubkey.toBase58(), PAYER) + }) + + it('supports a compatible custom pool program', async () => { + const poolProgramAddress = Keypair.generate().publicKey.toBase58() + const unsigned = await generate({ poolType: undefined, poolProgramAddress }) + + assert.equal(unsigned.instructions[0]?.programId.toBase58(), poolProgramAddress) + }) + }) + + describe('validation', () => { + it('rejects invalid public keys', async () => { + for (const [opts, param] of [ + [{ tokenAddress: 'invalid' }, 'tokenAddress'], + [{ newOwner: 'invalid' }, 'newOwner'], + [{ newOwner: PublicKey.default.toBase58() }, 'newOwner'], + ]) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + + it('rejects the current pool owner', async () => { + await assert.rejects( + () => generate({ newOwner: OWNER.toBase58() }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'transferOwnership' && + err.context.param === 'newOwner' && + err.message.includes('must not be the current pool owner'), + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new TransferOwnership().execute(submitChain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + newOwner: NEW_OWNER, + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-wallet authority for signed transfer', async () => { + await assert.rejects( + () => + new TransferOwnership().execute(chain(), { + tokenAddress: TOKEN, + poolType: 'burn-mint', + newOwner: NEW_OWNER, + authority: AUTHORITY, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'transferOwnership' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/transfer-ownership.ts b/ccip-sdk/src/cct/solana/token-pool/operations/transfer-ownership.ts new file mode 100644 index 000000000..f6c571bcf --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/transfer-ownership.ts @@ -0,0 +1,136 @@ +import { PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type PoolProgramRef, + createTokenPoolProgram, + deriveTokenPoolConfigPda, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + parsePublicKey, + resolvePoolProgram, + validateAuthorityMatchesWallet, +} from '../../validate.ts' +import { GetTokenPoolState } from './get-token-pool-state.ts' + +/** Parameters shared by Solana token pool ownership-transfer generation and execution. */ +type TransferOwnershipParams = PoolProgramRef & { + /** Token mint address managed by the pool. */ + tokenAddress: string + /** Address proposed as the next pool owner. It must accept ownership separately. */ + newOwner: string + /** Current pool owner. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type ParsedTransferOwnershipParams = { + tokenAddress: PublicKey + newOwner: PublicKey + poolProgram: PublicKey + payer: PublicKey + authority: PublicKey +} + +/** Parameters for unsigned Solana token pool ownership transfer. */ +export type GenerateTransferOwnershipParams = SolanaGenerateParams + +/** Unsigned Solana token pool ownership transfer result. */ +export type GenerateTransferOwnershipResult = UnsignedSolanaTx + +/** Parameters for executing Solana token pool ownership transfer. */ +export type ExecuteTransferOwnershipParams = SolanaExecuteParams + +/** Result of executing Solana token pool ownership transfer. */ +export type ExecuteTransferOwnershipResult = TransactionResult + +/** Proposes a new owner for a Solana token pool. The proposed owner must accept separately. */ +export class TransferOwnership extends SolanaOperation< + TransferOwnershipParams, + UnsignedSolanaTx, + ParsedTransferOwnershipParams +> { + readonly name = 'transferOwnership' + + /** Parses public keys and defaults authority to payer without mutating caller params. */ + protected override parse(params: GenerateTransferOwnershipParams): ParsedTransferOwnershipParams { + const payer = parsePublicKey(this.name, 'payer', params.payer) + const newOwner = parsePublicKey(this.name, 'newOwner', params.newOwner) + if (newOwner.equals(PublicKey.default)) { + throw new CCTParamsInvalidError( + this.name, + 'newOwner', + 'must not be the default public key or zero address', + ) + } + + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + newOwner, + poolProgram: resolvePoolProgram(this.name, params), + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + } + } + + /** Reads the pool state to reject self-transfer, then builds the unsigned Solana `transferOwnership` instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedTransferOwnershipParams, + ): Promise { + const { config } = await new GetTokenPoolState().query(chain, { + tokenAddress: opts.tokenAddress.toBase58(), + poolProgramAddress: opts.poolProgram.toBase58(), + }) + + if (opts.newOwner.equals(new PublicKey(config.owner))) { + throw new CCTParamsInvalidError(this.name, 'newOwner', 'must not be the current pool owner') + } + + const program = createTokenPoolProgram(chain, opts.poolProgram, opts.payer) + const instruction = await program.methods + .transferOwnership(opts.newOwner) + .accountsStrict({ + state: deriveTokenPoolConfigPda(opts.poolProgram, opts.tokenAddress), + mint: opts.tokenAddress, + authority: opts.authority, + }) + .instruction() + + chain.logger.debug( + `${this.name}: token = ${opts.tokenAddress.toBase58()}, poolProgram = ${opts.poolProgram.toBase58()}`, + ) + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the current pool owner wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteTransferOwnershipParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'transferOwnership requires authority to be the executing wallet. Use generateUnsignedTransferOwnership for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/withdraw-liquidity.test.ts b/ccip-sdk/src/cct/solana/token-pool/operations/withdraw-liquidity.test.ts new file mode 100644 index 000000000..f65f582a3 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/withdraw-liquidity.test.ts @@ -0,0 +1,290 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { BorshAccountsCoder } from '@coral-xyz/anchor' +import { AccountLayout, TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync } from '@solana/spl-token' +import { Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import { lockReleaseTokenPoolCoder } from '../../../../solana/idl/token-pool-coder.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError, CCTTxFailedError } from '../../../errors.ts' +import { + deriveTokenPoolConfigPda, + deriveTokenPoolSignerPda, + resolveTokenPoolProgram, +} from '../../programs/token-pool.ts' +import { WithdrawLiquidity } from './withdraw-liquidity.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function tokenAccount(owner: PublicKey, amount = 1_000_000n) { + const data = Buffer.alloc(AccountLayout.span) + AccountLayout.encode( + { + mint: new PublicKey(TOKEN), + owner, + amount, + delegateOption: 0, + delegate: PublicKey.default, + state: 1, + isNativeOption: 0, + isNative: 0n, + delegatedAmount: 0n, + closeAuthorityOption: 0, + closeAuthority: PublicKey.default, + }, + data, + ) + return { owner: TOKEN_PROGRAM_ID, data } +} + +function poolState(poolProgram: PublicKey, rebalancer = new PublicKey(AUTHORITY), accepts = true) { + const mint = new PublicKey(TOKEN) + const poolSigner = deriveTokenPoolSignerPda(poolProgram, mint) + return Buffer.concat([ + BorshAccountsCoder.accountDiscriminator('State'), + Buffer.from([1]), + TOKEN_PROGRAM_ID.toBuffer(), + mint.toBuffer(), + Buffer.from([9]), + poolSigner.toBuffer(), + PublicKey.default.toBuffer(), + new PublicKey(AUTHORITY).toBuffer(), + PublicKey.default.toBuffer(), + PublicKey.default.toBuffer(), + PublicKey.default.toBuffer(), + PublicKey.default.toBuffer(), + rebalancer.toBuffer(), + Buffer.from([accepts ? 1 : 0, 0]), + Buffer.alloc(4), + PublicKey.default.toBuffer(), + ]) +} + +function chain( + poolProgram = resolveTokenPoolProgram('lock-release'), + rebalancer = new PublicKey(AUTHORITY), + acceptsLiquidity = true, + poolBalance = 1_000_000n, +): SolanaChain { + const mint = new PublicKey(TOKEN) + const state = deriveTokenPoolConfigPda(poolProgram, mint) + const poolSigner = deriveTokenPoolSignerPda(poolProgram, mint) + const poolTokenAccount = getAssociatedTokenAddressSync(mint, poolSigner, true) + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + getAccountInfo: async (address: PublicKey) => + address.equals(state) + ? { owner: poolProgram, data: poolState(poolProgram, rebalancer, acceptsLiquidity) } + : address.equals(poolTokenAccount) + ? tokenAccount(poolSigner, poolBalance) + : tokenAccount(rebalancer), + }, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + const poolProgram = resolveTokenPoolProgram('lock-release') + const mint = new PublicKey(TOKEN) + const state = deriveTokenPoolConfigPda(poolProgram, mint) + const poolSigner = deriveTokenPoolSignerPda(poolProgram, mint) + const poolTokenAccount = getAssociatedTokenAddressSync(mint, poolSigner, true) + return Object.assign(chain(), { + connection: { + getAccountInfo: async (address: PublicKey) => + address.equals(state) + ? { owner: poolProgram, data: poolState(poolProgram, WALLET.publicKey) } + : address.equals(poolTokenAccount) + ? tokenAccount(poolSigner) + : tokenAccount(WALLET.publicKey), + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts = {}) { + return new WithdrawLiquidity().generate(chain(), { + tokenAddress: TOKEN, + poolType: 'lock-release', + payer: PAYER, + authority: AUTHORITY, + amount: 1_000_000n, + ...opts, + }) +} + +describe('WithdrawLiquidity (cct/solana)', () => { + describe('generate', () => { + it('builds the lock-release pool liquidity instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + const poolProgram = resolveTokenPoolProgram('lock-release') + const token = new PublicKey(TOKEN) + const poolSigner = deriveTokenPoolSignerPda(poolProgram, token) + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction!.programId.toBase58(), poolProgram.toBase58()) + assert.deepEqual( + instruction!.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { + pubkey: deriveTokenPoolConfigPda(poolProgram, token).toBase58(), + isSigner: false, + isWritable: false, + }, + { pubkey: TOKEN_PROGRAM_ID.toBase58(), isSigner: false, isWritable: false }, + { pubkey: TOKEN, isSigner: false, isWritable: true }, + { pubkey: poolSigner.toBase58(), isSigner: false, isWritable: false }, + { + pubkey: getAssociatedTokenAddressSync(token, poolSigner, true).toBase58(), + isSigner: false, + isWritable: true, + }, + { + pubkey: getAssociatedTokenAddressSync(token, new PublicKey(AUTHORITY), true).toBase58(), + isSigner: false, + isWritable: true, + }, + { pubkey: AUTHORITY, isSigner: true, isWritable: false }, + ], + ) + const decoded = lockReleaseTokenPoolCoder.instruction.decode(instruction!.data) + assert.ok(decoded) + assert.equal(decoded.name, 'withdrawLiquidity') + assert.equal( + (decoded.data as { amount: { toString(): string } }).amount.toString(), + '1000000', + ) + }) + + it('explains failed liquidity preflight checks', async () => { + for (const [pool, hint] of [ + [chain(resolveTokenPoolProgram('lock-release'), PublicKey.default), 'setRebalancer'], + [ + chain(resolveTokenPoolProgram('lock-release'), new PublicKey(AUTHORITY), false), + 'setCanAcceptLiquidity(true)', + ], + [ + chain(resolveTokenPoolProgram('lock-release'), new PublicKey(AUTHORITY), true, 0n), + 'pool token account', + ], + ] as const) { + await assert.rejects( + () => + new WithdrawLiquidity().generate(pool, { + tokenAddress: TOKEN, + poolType: 'lock-release', + payer: PAYER, + authority: AUTHORITY, + amount: 1n, + }), + (error: unknown) => error instanceof CCTTxFailedError && error.message.includes(hint), + ) + } + }) + + it('defaults authority to payer', async () => { + const unsigned = await new WithdrawLiquidity().generate( + chain(resolveTokenPoolProgram('lock-release'), new PublicKey(PAYER)), + { + tokenAddress: TOKEN, + poolType: 'lock-release', + payer: PAYER, + amount: 1_000_000n, + }, + ) + + assert.equal(unsigned.instructions[0]!.keys[6]!.pubkey.toBase58(), PAYER) + }) + + it('supports a compatible custom pool program', async () => { + const poolProgramAddress = Keypair.generate().publicKey.toBase58() + const unsigned = await new WithdrawLiquidity().generate( + chain(new PublicKey(poolProgramAddress)), + { + tokenAddress: TOKEN, + poolProgramAddress, + payer: PAYER, + authority: AUTHORITY, + amount: 1_000_000n, + }, + ) + + assert.equal(unsigned.instructions[0]?.programId.toBase58(), poolProgramAddress) + }) + }) + + describe('validation', () => { + it('rejects invalid public keys, amounts, and burn-mint pools', async () => { + for (const [opts, param] of [ + [{ tokenAddress: 'invalid' }, 'tokenAddress'], + [{ authority: 'invalid' }, 'authority'], + [{ amount: 0n }, 'amount'], + [{ amount: 0x1_0000_0000_0000_0000n }, 'amount'], + [{ poolType: 'burn-mint' as const }, 'poolType'], + [ + { + poolType: undefined, + poolProgramAddress: resolveTokenPoolProgram('burn-mint').toBase58(), + }, + 'poolProgramAddress', + ], + ]) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new WithdrawLiquidity().execute(submitChain(), { + tokenAddress: TOKEN, + poolType: 'lock-release', + amount: 1_000_000n, + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-wallet authority for signed liquidity withdrawal', async () => { + await assert.rejects( + () => + new WithdrawLiquidity().execute(chain(), { + tokenAddress: TOKEN, + poolType: 'lock-release', + amount: 1_000_000n, + authority: AUTHORITY, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'withdrawLiquidity' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token-pool/operations/withdraw-liquidity.ts b/ccip-sdk/src/cct/solana/token-pool/operations/withdraw-liquidity.ts new file mode 100644 index 000000000..ed6a16255 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token-pool/operations/withdraw-liquidity.ts @@ -0,0 +1,161 @@ +import type { PublicKey } from '@solana/web3.js' +import BN from 'bn.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTTxFailedError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { + type CustomPoolProgramRef, + type LockReleasePoolProgramRef, + createLockReleaseTokenPoolProgram, + deriveTokenPoolConfigPda, + deriveTokenPoolSignerPda, +} from '../../programs/token-pool.ts' +import { submit } from '../../submit.ts' +import { + U64_MAX, + parsePublicKey, + resolveExistingTokenAccount, + resolveLockReleasePoolProgram, + validateAuthorityMatchesWallet, + validateBigInt, + validatePoolLiquidityConfig, +} from '../../validate.ts' + +type PoolProgramRef = LockReleasePoolProgramRef | CustomPoolProgramRef + +type WithdrawLiquidityParams = PoolProgramRef & { + /** Token mint address managed by the lock-release pool. */ + tokenAddress: string + /** Amount to withdraw in base units. Must be a positive u64. */ + amount: bigint + /** Pool rebalancer that withdraws liquidity. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type ParsedWithdrawLiquidityParams = { + tokenAddress: PublicKey + amount: bigint + poolProgram: PublicKey + payer: PublicKey + authority: PublicKey +} + +/** Parameters for unsigned Solana lock-release pool liquidity withdrawal. */ +export type GenerateWithdrawLiquidityParams = SolanaGenerateParams + +/** Unsigned Solana lock-release pool liquidity withdrawal result. */ +export type GenerateWithdrawLiquidityResult = UnsignedSolanaTx + +/** Parameters for withdrawing Solana lock-release pool liquidity. */ +export type ExecuteWithdrawLiquidityParams = SolanaExecuteParams + +/** Result of withdrawing Solana lock-release pool liquidity. */ +export type ExecuteWithdrawLiquidityResult = TransactionResult + +/** Withdraws tokens from a lock-release pool into a rebalancer's associated token account. */ +export class WithdrawLiquidity extends SolanaOperation< + WithdrawLiquidityParams, + UnsignedSolanaTx, + ParsedWithdrawLiquidityParams +> { + readonly name = 'withdrawLiquidity' + + /** Parses public keys, validates amount, and defaults authority to payer without mutating caller params. */ + protected override parse(params: GenerateWithdrawLiquidityParams): ParsedWithdrawLiquidityParams { + validateBigInt(this.name, 'amount', params.amount, 1n, U64_MAX) + + const poolProgram = resolveLockReleasePoolProgram(this.name, params) + const payer = parsePublicKey(this.name, 'payer', params.payer) + + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + amount: params.amount, + poolProgram, + payer, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + } + } + + /** Builds the unsigned Solana `withdrawLiquidity` instruction for a lock-release pool. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedWithdrawLiquidityParams, + ): Promise { + // The caller must be the configured rebalancer and the pool must accept withdrawals. + await validatePoolLiquidityConfig( + this.name, + chain, + opts.poolProgram, + opts.tokenAddress, + opts.authority, + ) + + // The rebalancer's destination ATA must exist. + const { tokenAccount: remoteTokenAccount, tokenProgram } = await resolveExistingTokenAccount( + chain.connection, + opts.tokenAddress, + opts.authority, + ) + const poolSigner = deriveTokenPoolSignerPda(opts.poolProgram, opts.tokenAddress) + + // The pool vault ATA must have been created during pool initialization and hold the withdrawal. + const { tokenAccount: poolTokenAccount, account: poolTokenAccountInfo } = + await resolveExistingTokenAccount(chain.connection, opts.tokenAddress, poolSigner) + + // Avoid an opaque SPL Token insufficient-funds failure. + if (poolTokenAccountInfo.amount < opts.amount) { + throw new CCTTxFailedError( + this.name, + `pool token account ${poolTokenAccount.toBase58()} has ${poolTokenAccountInfo.amount}, but ${opts.amount} is required`, + ) + } + + const instruction = await createLockReleaseTokenPoolProgram(chain, opts.poolProgram, opts.payer) + .methods.withdrawLiquidity(new BN(opts.amount.toString())) + .accountsStrict({ + state: deriveTokenPoolConfigPda(opts.poolProgram, opts.tokenAddress), + tokenProgram, + mint: opts.tokenAddress, + poolSigner, + poolTokenAccount, + remoteTokenAccount, + authority: opts.authority, + }) + .instruction() + + chain.logger.debug( + `${this.name}: token = ${opts.tokenAddress.toBase58()}, poolProgram = ${opts.poolProgram.toBase58()}, amount = ${opts.amount}`, + ) + return { family: ChainFamily.Solana, instructions: [instruction], mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the rebalancer wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteWithdrawLiquidityParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'withdrawLiquidity requires authority to be the executing wallet. Use generateUnsignedWithdrawLiquidity for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token/constants.ts b/ccip-sdk/src/cct/solana/token/constants.ts new file mode 100644 index 000000000..76061c0cb --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/constants.ts @@ -0,0 +1,10 @@ +import { PublicKey } from '@solana/web3.js' + +/** Metaplex Token Metadata program address. */ +export const METADATA_PROGRAM_ID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s') + +/** SPL Token authority roles that can be set. */ +export const TOKEN_AUTHORITY_TYPES = { + MINT: 'mint', + FREEZE: 'freeze', +} as const diff --git a/ccip-sdk/src/cct/solana/token/operations/approve-token.test.ts b/ccip-sdk/src/cct/solana/token/operations/approve-token.test.ts new file mode 100644 index 000000000..3f1e2aa79 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/operations/approve-token.test.ts @@ -0,0 +1,220 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { + TOKEN_2022_PROGRAM_ID, + TOKEN_PROGRAM_ID, + getAssociatedTokenAddressSync, +} from '@solana/spl-token' +import { Keypair, PublicKey } from '@solana/web3.js' + +import { + CCIPTokenAccountNotFoundError, + CCIPTokenMintInvalidError, + CCIPTokenMintNotFoundError, +} from '../../../../errors/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { U64_MAX } from '../../validate.ts' +import { ApproveToken } from './approve-token.ts' + +const TOKEN = Keypair.generate().publicKey +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const DELEGATE = Keypair.generate().publicKey.toBase58() +const TOKEN_ACCOUNT = Keypair.generate().publicKey.toBase58() +const MULTISIG = Keypair.generate().publicKey.toBase58() +const MULTISIG_SIGNER = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { publicKey: Keypair.generate().publicKey, signTransaction: async (tx: T) => tx } + +function chain( + mintOwner: PublicKey | null = TOKEN_PROGRAM_ID, + tokenAccountExists = true, +): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + getAccountInfo: async (address: PublicKey) => { + if (!mintOwner || address.equals(TOKEN)) return mintOwner ? { owner: mintOwner } : null + return tokenAccountExists ? { owner: mintOwner, data: Buffer.alloc(165) } : null + }, + }, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(chain(), { + connection: { + getAccountInfo: async () => ({ owner: TOKEN_PROGRAM_ID, data: Buffer.alloc(165) }), + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts: Record = {}, mintOwner?: PublicKey | null) { + return new ApproveToken().generate(chain(mintOwner), { + payer: PAYER, + tokenAddress: TOKEN.toBase58(), + delegate: DELEGATE, + authority: AUTHORITY, + amount: 1_000_000n, + ...opts, + }) +} + +describe('ApproveToken (cct/solana)', () => { + describe('generate', () => { + it('derives the authority ATA and approves the delegate', async () => { + const unsigned = await generate() + const ata = getAssociatedTokenAddressSync( + TOKEN, + new PublicKey(AUTHORITY), + true, + TOKEN_PROGRAM_ID, + ) + const [instruction] = unsigned.instructions + + assert.ok(instruction) + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction.programId.toBase58(), TOKEN_PROGRAM_ID.toBase58()) + assert.equal(instruction.data[0], 4) // Approve + assert.equal(instruction.data.readBigUInt64LE(1), 1_000_000n) + assert.deepEqual( + instruction.keys.slice(0, 3).map(({ pubkey, isSigner }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + })), + [ + { pubkey: ata.toBase58(), isSigner: false }, + { pubkey: DELEGATE, isSigner: false }, + { pubkey: AUTHORITY, isSigner: true }, + ], + ) + }) + + it('uses an explicitly supplied token account', async () => { + const unsigned = await generate({ tokenAccount: TOKEN_ACCOUNT }) + assert.equal(unsigned.instructions[0]!.keys[0]!.pubkey.toBase58(), TOKEN_ACCOUNT) + }) + + it('supports Token-2022 multisig authorities', async () => { + const unsigned = await generate( + { authority: MULTISIG, multisigSigners: [MULTISIG_SIGNER] }, + TOKEN_2022_PROGRAM_ID, + ) + const ata = getAssociatedTokenAddressSync( + TOKEN, + new PublicKey(MULTISIG), + true, + TOKEN_2022_PROGRAM_ID, + ) + const [instruction] = unsigned.instructions + + assert.ok(instruction) + assert.equal(instruction.programId.toBase58(), TOKEN_2022_PROGRAM_ID.toBase58()) + assert.equal(instruction.keys[0]!.pubkey.toBase58(), ata.toBase58()) + assert.deepEqual( + instruction.keys + .slice(2) + .map(({ pubkey, isSigner }) => ({ pubkey: pubkey.toBase58(), isSigner })), + [ + { pubkey: MULTISIG, isSigner: false }, + { pubkey: MULTISIG_SIGNER, isSigner: true }, + ], + ) + }) + + it('defaults authority to payer and supports zero through maximum u64 allowances', async () => { + const zero = await generate({ amount: 0n }) + const maximum = await generate({ authority: undefined, amount: U64_MAX }) + + assert.equal(zero.instructions[0]!.data.readBigUInt64LE(1), 0n) + assert.equal(maximum.instructions[0]!.keys[2]!.pubkey.toBase58(), PAYER) + assert.equal(maximum.instructions[0]!.data.readBigUInt64LE(1), U64_MAX) + }) + }) + + describe('validation', () => { + it('rejects invalid parameters', async () => { + for (const [opts, param] of [ + [{ tokenAddress: 'invalid' }, 'tokenAddress'], + [{ tokenAccount: 'invalid' }, 'tokenAccount'], + [{ delegate: 'invalid' }, 'delegate'], + [{ authority: 'invalid' }, 'authority'], + [{ amount: 1 }, 'amount'], + [{ amount: U64_MAX + 1n }, 'amount'], + [{ multisigSigners: 'invalid' }, 'multisigSigners'], + [{ multisigSigners: ['invalid'] }, 'multisigSigners[0]'], + ] as const) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + + it('rejects a missing token account before submission', async () => { + await assert.rejects( + () => + new ApproveToken().generate(chain(TOKEN_PROGRAM_ID, false), { + payer: PAYER, + tokenAddress: TOKEN.toBase58(), + delegate: DELEGATE, + amount: 1n, + }), + (err: unknown) => err instanceof CCIPTokenAccountNotFoundError, + ) + }) + + it('rejects missing and non-token mints', async () => { + await assert.rejects( + () => generate({}, null), + (err: unknown) => err instanceof CCIPTokenMintNotFoundError, + ) + await assert.rejects( + () => generate({}, Keypair.generate().publicKey), + (err: unknown) => err instanceof CCIPTokenMintInvalidError, + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new ApproveToken().execute(submitChain(), { + tokenAddress: TOKEN.toBase58(), + delegate: DELEGATE, + amount: 1n, + wallet: WALLET, + }) + assert.deepEqual(result, { hash: HASH }) + }) + + it('requires unsigned generation for SPL multisig and external authorities', async () => { + for (const opts of [ + { authority: MULTISIG, multisigSigners: [MULTISIG_SIGNER] }, + { authority: AUTHORITY }, + ]) { + await assert.rejects( + () => + new ApproveToken().execute(chain(), { + tokenAddress: TOKEN.toBase58(), + delegate: DELEGATE, + amount: 1n, + wallet: WALLET, + ...opts, + }), + (err: unknown) => err instanceof CCTParamsInvalidError, + ) + } + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token/operations/approve-token.ts b/ccip-sdk/src/cct/solana/token/operations/approve-token.ts new file mode 100644 index 000000000..c6254ed36 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/operations/approve-token.ts @@ -0,0 +1,151 @@ +import { createApproveInstruction } from '@solana/spl-token' +import type { PublicKey, TransactionInstruction } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { submit } from '../../submit.ts' +import { + U64_MAX, + parsePublicKey, + resolveExistingTokenAccount, + validateAuthorityMatchesWallet, + validateBigInt, +} from '../../validate.ts' + +type ApproveTokenParams = { + /** SPL token mint address. */ + tokenAddress: string + /** Token account to approve from. Defaults to the authority's existing associated token account. */ + tokenAccount?: string + /** Trusted delegate address authorized to transfer tokens. Re-approval replaces the current delegate. */ + delegate: string + /** + * Allowance in base units (not human-readable tokens). Re-approval replaces the current allowance; + * use `0n` to clear it. E.g., 1_000_000n with 6 decimals = 1 token. Maximum u64: 2^64 - 1. + */ + amount: bigint + /** Token account owner. Defaults to `payer` for single-signer transactions. */ + authority?: string + /** SPL Token multisig member addresses. Required when authority is an SPL Token multisig. */ + multisigSigners?: string[] +} + +type ParsedApproveTokenParams = { + tokenAddress: PublicKey + tokenAccount?: PublicKey + delegate: PublicKey + amount: bigint + authority: PublicKey + multisigSigners: PublicKey[] +} + +/** Parameters for unsigned Solana SPL Token delegate approval. */ +export type GenerateApproveTokenParams = SolanaGenerateParams + +/** Unsigned Solana SPL Token delegate approval result. */ +export type GenerateApproveTokenResult = UnsignedSolanaTx + +/** Parameters for executing Solana SPL Token delegate approval. */ +export type ExecuteApproveTokenParams = SolanaExecuteParams + +/** Result of executing Solana SPL Token delegate approval. */ +export type ExecuteApproveTokenResult = TransactionResult + +/** Approves a delegate to transfer up to an allowance from an SPL token account. */ +export class ApproveToken extends SolanaOperation< + ApproveTokenParams, + UnsignedSolanaTx, + ParsedApproveTokenParams +> { + readonly name = 'approveToken' + + /** Parses public keys, allowance, and optional SPL Token multisig signers. */ + protected override parse(params: GenerateApproveTokenParams): ParsedApproveTokenParams { + validateBigInt(this.name, 'amount', params.amount, 0n, U64_MAX) + if (params.multisigSigners !== undefined && !Array.isArray(params.multisigSigners)) { + throw new CCTParamsInvalidError(this.name, 'multisigSigners', 'must be an array') + } + + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + tokenAccount: + params.tokenAccount === undefined + ? undefined + : parsePublicKey(this.name, 'tokenAccount', params.tokenAccount), + delegate: parsePublicKey(this.name, 'delegate', params.delegate), + amount: params.amount, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + multisigSigners: (params.multisigSigners ?? []).map((signer, i) => + parsePublicKey(this.name, `multisigSigners[${i}]`, signer), + ), + } + } + + /** Builds an SPL Token `Approve` instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedApproveTokenParams, + ): Promise { + const { tokenAccount, tokenProgram } = await resolveExistingTokenAccount( + chain.connection, + opts.tokenAddress, + opts.authority, + opts.tokenAccount, + ) + + const instructions: TransactionInstruction[] = [ + createApproveInstruction( + tokenAccount, + opts.delegate, + opts.authority, + opts.amount, + opts.multisigSigners, + tokenProgram, + ), + ] + + chain.logger.debug( + `${this.name}: token = ${opts.tokenAddress.toBase58()}, tokenAccount = ${tokenAccount.toBase58()}, delegate = ${opts.delegate.toBase58()}, amount = ${opts.amount}`, + ) + return { family: ChainFamily.Solana, instructions, mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the token account owner wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteApproveTokenParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (parsed.multisigSigners.length > 0) { + throw new CCTParamsInvalidError( + this.name, + 'multisigSigners', + 'requires externally signed transactions; use generateUnsignedApproveToken', + ) + } + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'approveToken requires authority to be the executing wallet. Use generateUnsignedApproveToken for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token/operations/create-token-account.test.ts b/ccip-sdk/src/cct/solana/token/operations/create-token-account.test.ts new file mode 100644 index 000000000..584fbb308 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/operations/create-token-account.test.ts @@ -0,0 +1,142 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID, + TOKEN_PROGRAM_ID, + getAssociatedTokenAddressSync, +} from '@solana/spl-token' +import { Keypair, PublicKey } from '@solana/web3.js' + +import { + CCIPTokenMintInvalidError, + CCIPTokenMintNotFoundError, + CCIPWalletInvalidError, +} from '../../../../errors/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CreateTokenAccount } from './create-token-account.ts' + +const PAYER = Keypair.generate().publicKey.toBase58() +const MINT = Keypair.generate().publicKey +const OWNER = Keypair.generate().publicKey +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function stubChain(mintOwner: PublicKey | null = TOKEN_2022_PROGRAM_ID): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + getAccountInfo: async () => (mintOwner ? { owner: mintOwner } : null), + }, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(stubChain(), { + connection: { + getAccountInfo: async () => ({ owner: TOKEN_2022_PROGRAM_ID }), + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts = {}, mintOwner?: PublicKey | null) { + return new CreateTokenAccount().generate(stubChain(mintOwner), { + payer: PAYER, + tokenAddress: MINT.toBase58(), + ownerAddress: OWNER.toBase58(), + ...opts, + }) +} + +describe('CreateTokenAccount (cct/solana)', () => { + describe('generate', () => { + it('builds an idempotent ATA create instruction for any owner', async () => { + const unsigned = await generate() + const [ix] = unsigned.instructions + const ata = getAssociatedTokenAddressSync(MINT, OWNER, true, TOKEN_2022_PROGRAM_ID) + + assert.ok(ix) + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(unsigned.tokenAccountAddress, ata.toBase58()) + assert.equal(ix.programId.toBase58(), ASSOCIATED_TOKEN_PROGRAM_ID.toBase58()) + assert.equal(ix.data.length, 1) + assert.equal(ix.data[0], 1) // CreateIdempotent + assert.equal(ix.keys[0]!.pubkey.toBase58(), PAYER) + assert.equal(ix.keys[1]!.pubkey.toBase58(), ata.toBase58()) + assert.equal(ix.keys[2]!.pubkey.toBase58(), OWNER.toBase58()) + assert.equal(ix.keys[3]!.pubkey.toBase58(), MINT.toBase58()) + assert.equal(ix.keys.at(-1)!.pubkey.toBase58(), TOKEN_2022_PROGRAM_ID.toBase58()) + }) + + it('builds for legacy SPL Token mints', async () => { + const unsigned = await generate({}, TOKEN_PROGRAM_ID) + const ata = getAssociatedTokenAddressSync(MINT, OWNER, true, TOKEN_PROGRAM_ID) + + assert.equal(unsigned.tokenAccountAddress, ata.toBase58()) + assert.equal( + unsigned.instructions[0]!.keys.at(-1)!.pubkey.toBase58(), + TOKEN_PROGRAM_ID.toBase58(), + ) + }) + }) + + describe('validation', () => { + it('rejects a missing mint', async () => { + await assert.rejects( + () => generate({}, null), + (err: unknown) => err instanceof CCIPTokenMintNotFoundError, + ) + }) + + it('rejects non-token mint accounts', async () => { + await assert.rejects( + () => generate({}, Keypair.generate().publicKey), + (err: unknown) => err instanceof CCIPTokenMintInvalidError, + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the token account address', async () => { + const result = await new CreateTokenAccount().execute(submitChain(), { + tokenAddress: MINT.toBase58(), + ownerAddress: OWNER.toBase58(), + wallet: WALLET, + }) + + assert.deepEqual(result, { + hash: HASH, + tokenAccountAddress: getAssociatedTokenAddressSync( + MINT, + OWNER, + true, + TOKEN_2022_PROGRAM_ID, + ).toBase58(), + }) + }) + + it('rejects an invalid wallet before generating instructions', async () => { + await assert.rejects( + new CreateTokenAccount().execute(stubChain(), { + tokenAddress: MINT.toBase58(), + ownerAddress: OWNER.toBase58(), + wallet: {}, + }), + CCIPWalletInvalidError, + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token/operations/create-token-account.ts b/ccip-sdk/src/cct/solana/token/operations/create-token-account.ts new file mode 100644 index 000000000..bb0941dc1 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/operations/create-token-account.ts @@ -0,0 +1,102 @@ +import { createAssociatedTokenAccountIdempotentInstruction } from '@solana/spl-token' +import type { PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { resolveATA } from '../../../../solana/utils.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { submit } from '../../submit.ts' +import { parsePublicKey } from '../../validate.ts' + +/** Parameters for deriving and creating a Solana associated token account. */ +type CreateTokenAccountParams = { + /** SPL token mint address for the associated token account. */ + tokenAddress: string + /** Wallet or PDA owner address for the associated token account. */ + ownerAddress: string +} + +/** Parameters for unsigned Solana associated token account creation. */ +export type GenerateCreateTokenAccountParams = SolanaGenerateParams + +type ParsedCreateTokenAccountParams = { + payer: PublicKey + tokenAddress: PublicKey + ownerAddress: PublicKey +} + +/** Unsigned associated token account creation tx plus the derived token account address. */ +export type GenerateCreateTokenAccountResult = UnsignedSolanaTx & { tokenAccountAddress: string } + +/** Parameters for executing Solana associated token account creation. */ +export type ExecuteCreateTokenAccountParams = SolanaExecuteParams + +/** Result of executing Solana associated token account creation. */ +export type ExecuteCreateTokenAccountResult = TransactionResult & { tokenAccountAddress: string } + +/** Creates an Associated Token Account for any wallet or PDA owner. */ +export class CreateTokenAccount extends SolanaOperation< + CreateTokenAccountParams, + GenerateCreateTokenAccountResult, + ParsedCreateTokenAccountParams +> { + readonly name = 'createTokenAccount' + + /** Parses create-token-account parameters. */ + protected override parse( + params: GenerateCreateTokenAccountParams, + ): ParsedCreateTokenAccountParams { + return { + payer: parsePublicKey(this.name, 'payer', params.payer), + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + ownerAddress: parsePublicKey(this.name, 'ownerAddress', params.ownerAddress), + } + } + + /** Builds an unsigned idempotent associated token account creation transaction. */ + protected async buildUnsigned( + chain: SolanaChain, + params: ParsedCreateTokenAccountParams, + ): Promise { + const { payer, tokenAddress: mint, ownerAddress: owner } = params + const { ata: tokenAccount, tokenProgram } = await resolveATA(chain.connection, mint, owner) + + chain.logger.debug( + `${this.name}: mint = ${mint.toBase58()}, owner = ${owner.toBase58()}, tokenAccount = ${tokenAccount.toBase58()}, tokenProgram = ${tokenProgram.toBase58()}`, + ) + + return { + family: ChainFamily.Solana, + instructions: [ + createAssociatedTokenAccountIdempotentInstruction( + payer, + tokenAccount, + owner, + mint, + tokenProgram, + ), + ], + mainIndex: 0, + tokenAccountAddress: tokenAccount.toBase58(), + } + } + + /** Generate, sign, simulate, send, confirm, and return the derived token account address. */ + override async execute( + chain: SolanaChain, + params: ExecuteCreateTokenAccountParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + const tx = await this.buildUnsigned(chain, parsed) + const hash = await submit(chain, wallet, tx, this.name, computeUnits) + + return { ...hash, tokenAccountAddress: tx.tokenAccountAddress } + } +} diff --git a/ccip-sdk/src/cct/solana/token/operations/deploy-token.test.ts b/ccip-sdk/src/cct/solana/token/operations/deploy-token.test.ts new file mode 100644 index 000000000..8a30dd918 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/operations/deploy-token.test.ts @@ -0,0 +1,223 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { Keypair, PublicKey, SystemProgram } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { U64_MAX } from '../../validate.ts' +import { DeployToken } from './deploy-token.ts' + +const BLOCKHASH = PublicKey.default.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const METAPLEX_PROGRAM = 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s' +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function stubChain(): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + rpcEndpoint: 'http://localhost:8899', + getAccountInfo: () => assert.fail('should not RPC before validation'), + getMinimumBalanceForRentExemption: async () => 123, + getLatestBlockhash: async () => ({ blockhash: BLOCKHASH, lastValidBlockHeight: 0 }), + }, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(stubChain(), { + connection: { + rpcEndpoint: 'http://localhost:8899', + getMinimumBalanceForRentExemption: async () => 123, + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ blockhash: BLOCKHASH, lastValidBlockHeight: 0 }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts: Record = {}) { + return new DeployToken().generate(stubChain(), { + decimals: 9, + withMetaplex: false, + payer: PAYER, + ...opts, + }) +} + +describe('DeployToken (cct/solana)', () => { + describe('generate', () => { + it('builds unsigned SPL mint create instructions', async () => { + const unsigned = await generate() + const [createAccountIx, initializeMintIx] = unsigned.instructions + + assert.ok(createAccountIx) + assert.ok(initializeMintIx) + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.match(unsigned.tokenAddress, /^[1-9A-HJ-NP-Za-km-z]+$/) + assert.equal('seed' in unsigned, false) + assert.equal(unsigned.metadataAddress, undefined) + assert.equal(unsigned.instructions.length, 2) + assert.equal(createAccountIx.programId.toBase58(), SystemProgram.programId.toBase58()) + assert.equal(initializeMintIx.programId.toBase58(), TOKEN_PROGRAM_ID.toBase58()) + assert.equal(initializeMintIx.data[0], 20) // InitializeMint2, not legacy InitializeMint + }) + + it('uses caller seed for reproducible mint address and supports no freeze authority', async () => { + const a = await generate({ seed: 'mint_seed', freezeAuthority: null }) + const b = await generate({ seed: 'mint_seed' }) + + assert.equal(a.tokenAddress, b.tokenAddress) + }) + + it('adds Metaplex metadata when requested', async () => { + const unsigned = await generate({ + withMetaplex: true, + name: 'My Token', + symbol: 'MTK', + }) + + assert.equal(unsigned.instructions.length, 3) + assert.match(unsigned.metadataAddress!, /^[1-9A-HJ-NP-Za-km-z]+$/) + assert.equal(unsigned.instructions[2]!.programId.toBase58(), METAPLEX_PROGRAM) + assert.equal(unsigned.instructions[2]!.data[0], 42) // createV1 + }) + + it('uses Token-2022 program for mint and metadata', async () => { + const unsigned = await generate({ + tokenProgram: 'token-2022', + withMetaplex: true, + name: 'My Token', + symbol: 'MTK', + }) + + assert.equal(unsigned.instructions[1]!.programId.toBase58(), TOKEN_2022_PROGRAM_ID.toBase58()) + assert.ok( + unsigned.instructions[2]!.keys.some( + (key) => key.pubkey.toBase58() === TOKEN_2022_PROGRAM_ID.toBase58(), + ), + ) + }) + + it('adds ATA creation and mintTo instructions for preMint', async () => { + const unsigned = await generate({ + preMint: 100n, + preMintRecipient: Keypair.generate().publicKey.toBase58(), + }) + + assert.equal(unsigned.instructions.length, 4) + assert.equal(unsigned.instructions[3]!.data[0], 7) // MintTo + }) + }) + + describe('validation', () => { + it('rejects invalid base and pre-mint parameters', async () => { + for (const [opts, param] of [ + [{ decimals: 256 }, 'decimals'], + [{ tokenProgram: 'invalid' }, 'tokenProgram'], + [{ withMetaplex: 'yes' }, 'withMetaplex'], + [{ seed: '' }, 'seed'], + [{ mintAuthority: 'invalid' }, 'mintAuthority'], + [{ freezeAuthority: 'invalid' }, 'freezeAuthority'], + [{ preMint: 0n }, 'preMint'], + [{ preMint: 1 }, 'preMint'], + [{ preMint: U64_MAX + 1n }, 'preMint'], + [{ preMint: 1n }, 'preMintRecipient'], + [{ preMint: 1n, preMintRecipient: 'invalid' }, 'preMintRecipient'], + ] as const) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + + it('rejects invalid Metaplex name and URI parameters', async () => { + for (const [opts, param] of [ + [{ withMetaplex: true, name: '', symbol: 'MTK' }, 'name'], + [{ withMetaplex: true, name: 'My Token', symbol: 'MTK', uri: 1 }, 'uri'], + ] as const) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + + it('rejects seeds over 32 UTF-8 bytes', async () => { + await assert.rejects( + () => generate({ seed: '🚀'.repeat(9) }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'seed', + ) + }) + + it('validates Metaplex name and symbol by UTF-8 byte length', async () => { + await assert.rejects( + () => + generate({ + withMetaplex: true, + name: 'Valid', + symbol: '🚀🚀🚀', + }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'symbol', + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the mint address', async () => { + const result = await new DeployToken().execute(submitChain(), { + wallet: WALLET, + decimals: 9, + withMetaplex: false, + }) + + assert.equal(result.hash, HASH) + assert.match(result.tokenAddress, /^[1-9A-HJ-NP-Za-km-z]+$/) + }) + + it('returns the metadata address when creating Metaplex metadata', async () => { + const result = await new DeployToken().execute(submitChain(), { + wallet: WALLET, + decimals: 9, + withMetaplex: true, + name: 'My Token', + symbol: 'MTK', + }) + + assert.equal(result.hash, HASH) + assert.match(result.metadataAddress!, /^[1-9A-HJ-NP-Za-km-z]+$/) + }) + + it('rejects execute when preMint needs a non-wallet mintAuthority signer', async () => { + const wallet = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, + } + + await assert.rejects( + () => + new DeployToken().execute(stubChain(), { + wallet, + decimals: 9, + tokenProgram: 'spl-token', + withMetaplex: false, + mintAuthority: Keypair.generate().publicKey.toBase58(), + preMint: 100n, + preMintRecipient: Keypair.generate().publicKey.toBase58(), + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'mintAuthority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token/operations/deploy-token.ts b/ccip-sdk/src/cct/solana/token/operations/deploy-token.ts new file mode 100644 index 000000000..b44a211cc --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/operations/deploy-token.ts @@ -0,0 +1,372 @@ +import { + TOKEN_2022_PROGRAM_ID, + TOKEN_PROGRAM_ID, + createAssociatedTokenAccountIdempotentInstruction, + createInitializeMint2Instruction, + createMintToInstruction, + getAssociatedTokenAddressSync, + getMintLen, +} from '@solana/spl-token' +import { type TransactionInstruction, PublicKey, SystemProgram } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { deriveMetadataAddress } from '../../programs/token.ts' +import { submit } from '../../submit.ts' +import { + U64_MAX, + validateBigInt, + validateOptionalPublicKey, + validatePublicKey, +} from '../../validate.ts' + +type BaseDeployTokenParams = { + /** Mint decimals. Must be an integer between 0 and 255. */ + decimals: number + /** Token program that owns the mint: classic SPL Token or Token-2022. Defaults to spl-token. */ + tokenProgram?: 'spl-token' | 'token-2022' + /** Mint authority. Defaults to payer. */ + mintAuthority?: string + /** Freeze authority. Defaults to payer; set null to disable freezing. */ + freezeAuthority?: string | null + /** Initial supply in base units, between 1 and 2^64 - 1. Requires preMintRecipient. */ + preMint?: bigint + /** Recipient owner for the initial supply ATA. */ + preMintRecipient?: string + /** Seed for deterministic mint address derivation. Defaults to a random seed. Max 32 UTF-8 bytes. */ + seed?: string +} + +/** + * Parameters for creating a Solana SPL mint. + * + * Set `withMetaplex: true` to create Metaplex metadata; `name` and `symbol` are required; + */ +type DeployTokenParams = BaseDeployTokenParams & + ( + | { withMetaplex: false } + | { + withMetaplex: true + /** Token display name for Metaplex metadata. Max 32 UTF-8 bytes. */ + name: string + /** Token symbol for Metaplex metadata. Max 10 UTF-8 bytes. */ + symbol: string + /** Metadata URI for Metaplex metadata JSON. Optional; defaults to an empty string when omitted. */ + uri?: string | undefined + } + ) + +/** Parameters for unsigned Solana token deploy generation. */ +export type GenerateDeployTokenParams = SolanaGenerateParams + +/** Unsigned token deploy transaction plus the created mint address. */ +export type GenerateDeployTokenResult = UnsignedSolanaTx & { + tokenAddress: string + metadataAddress?: string +} + +/** Parameters for executing Solana token deploy. */ +export type ExecuteDeployTokenParams = SolanaExecuteParams + +/** Result of executing Solana token deploy. */ +export type ExecuteDeployTokenResult = TransactionResult & { + tokenAddress: string + metadataAddress?: string +} + +function utf8ByteLength(value: string): number { + return new TextEncoder().encode(value).length +} + +async function loadMetaplex() { + const [metadata, umi, bundleDefaults, web3] = await Promise.all([ + import('@metaplex-foundation/mpl-token-metadata'), + import('@metaplex-foundation/umi'), + import('@metaplex-foundation/umi-bundle-defaults'), + import('@metaplex-foundation/umi-web3js-adapters'), + ]) + + return { + TokenStandard: metadata.TokenStandard, + createNoopSigner: umi.createNoopSigner, + createUmi: bundleDefaults.createUmi, + createV1: metadata.createV1, + mplTokenMetadata: metadata.mplTokenMetadata, + percentAmount: umi.percentAmount, + publicKey: umi.publicKey, + signerIdentity: umi.signerIdentity, + toWeb3JsInstruction: web3.toWeb3JsInstruction, + } +} + +async function createMetadataInstructions( + chain: SolanaChain, + mint: PublicKey, + payer: PublicKey, + tokenProgram: PublicKey, + decimals: number, + mintAuthority: PublicKey, + params: { name: string; symbol: string; uri: string }, +): Promise { + const metaplex = await loadMetaplex() + const payerSigner = metaplex.createNoopSigner(metaplex.publicKey(payer.toBase58())) + const mintAuthoritySigner = metaplex.createNoopSigner( + metaplex.publicKey(mintAuthority.toBase58()), + ) + const metadataUmi = metaplex + .createUmi(chain.connection) + .use(metaplex.mplTokenMetadata()) + .use(metaplex.signerIdentity(payerSigner)) + + return metaplex + .createV1(metadataUmi, { + mint: metaplex.publicKey(mint.toBase58()), + authority: mintAuthoritySigner, + payer: payerSigner, + updateAuthority: mintAuthoritySigner, + splTokenProgram: metaplex.publicKey(tokenProgram.toBase58()), + name: params.name, + symbol: params.symbol, + uri: params.uri, + sellerFeeBasisPoints: metaplex.percentAmount(0), + decimals, + tokenStandard: metaplex.TokenStandard.Fungible, + }) + .getInstructions() + .map(metaplex.toWeb3JsInstruction) +} + +type DeployTokenConfig = { + payer: PublicKey + mintAuthority: PublicKey + freezeAuthority: PublicKey | null + tokenProgram: PublicKey + seed: string +} + +type ParsedDeployTokenParams = GenerateDeployTokenParams & { config: DeployTokenConfig } + +function resolveDeployTokenConfig(params: GenerateDeployTokenParams): DeployTokenConfig { + const payer = new PublicKey(params.payer) + return { + payer, + mintAuthority: new PublicKey(params.mintAuthority ?? params.payer), + freezeAuthority: + params.freezeAuthority === null + ? null + : new PublicKey(params.freezeAuthority ?? params.payer), + tokenProgram: params.tokenProgram === 'token-2022' ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID, + seed: params.seed ?? `mint_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, + } +} + +function createMintInstructions( + mint: PublicKey, + lamports: number, + decimals: number, + config: DeployTokenConfig, +): TransactionInstruction[] { + return [ + SystemProgram.createAccountWithSeed({ + fromPubkey: config.payer, + newAccountPubkey: mint, + basePubkey: config.payer, + seed: config.seed, + lamports, + space: getMintLen([]), + programId: config.tokenProgram, + }), + createInitializeMint2Instruction( + mint, + decimals, + config.mintAuthority, + config.freezeAuthority, + config.tokenProgram, + ), + ] +} + +function createPreMintInstructions( + mint: PublicKey, + params: GenerateDeployTokenParams, + config: DeployTokenConfig, +): TransactionInstruction[] { + if (params.preMint === undefined) return [] + + const recipient = new PublicKey(params.preMintRecipient!) + const ata = getAssociatedTokenAddressSync(mint, recipient, false, config.tokenProgram) + return [ + createAssociatedTokenAccountIdempotentInstruction( + config.payer, + ata, + recipient, + mint, + config.tokenProgram, + ), + createMintToInstruction( + mint, + ata, + config.mintAuthority, + params.preMint, + [], + config.tokenProgram, + ), + ] +} + +function getExternalMintAuthoritySigner( + params: DeployTokenParams, + payer: string, +): string | undefined { + const mintAuthority = params.mintAuthority ?? payer + return (params.withMetaplex || params.preMint !== undefined) && mintAuthority !== payer + ? mintAuthority + : undefined +} + +function validateBaseParams(operation: string, params: GenerateDeployTokenParams): void { + validatePublicKey(operation, 'payer', params.payer) + if (!Number.isInteger(params.decimals) || params.decimals < 0 || params.decimals > 255) { + throw new CCTParamsInvalidError(operation, 'decimals', 'must be an integer between 0 and 255') + } + if (params.tokenProgram && !['spl-token', 'token-2022'].includes(params.tokenProgram)) { + throw new CCTParamsInvalidError(operation, 'tokenProgram', 'must be spl-token or token-2022') + } + if (typeof params.withMetaplex !== 'boolean') { + throw new CCTParamsInvalidError(operation, 'withMetaplex', 'must be a boolean') + } + if (params.seed !== undefined && (!params.seed || utf8ByteLength(params.seed) > 32)) { + throw new CCTParamsInvalidError(operation, 'seed', 'must be non-empty and <= 32 UTF-8 bytes') + } + validateOptionalPublicKey(operation, 'mintAuthority', params.mintAuthority) + if (params.freezeAuthority !== undefined && params.freezeAuthority !== null) { + validatePublicKey(operation, 'freezeAuthority', params.freezeAuthority) + } +} + +function validatePreMintParams(operation: string, params: GenerateDeployTokenParams): void { + if (params.preMint !== undefined) { + validateBigInt(operation, 'preMint', params.preMint, 1n, U64_MAX) + } + if (params.preMint !== undefined && !params.preMintRecipient) { + throw new CCTParamsInvalidError( + operation, + 'preMintRecipient', + 'is required when preMint is set', + ) + } + validateOptionalPublicKey(operation, 'preMintRecipient', params.preMintRecipient) +} + +function validateMetaplexParams(operation: string, params: GenerateDeployTokenParams): void { + if (!params.withMetaplex) return + if (!params.name || utf8ByteLength(params.name) > 32) { + throw new CCTParamsInvalidError( + operation, + 'name', + 'is required and must be <= 32 UTF-8 bytes when withMetaplex is true', + ) + } + if (!params.symbol || utf8ByteLength(params.symbol) > 10) { + throw new CCTParamsInvalidError( + operation, + 'symbol', + 'is required and must be <= 10 UTF-8 bytes when withMetaplex is true', + ) + } + if (params.uri !== undefined && typeof params.uri !== 'string') { + throw new CCTParamsInvalidError(operation, 'uri', 'must be a string when provided') + } +} + +/** Creates a Solana SPL mint, optionally with Metaplex metadata and initial supply. */ +export class DeployToken extends SolanaOperation< + DeployTokenParams, + GenerateDeployTokenResult, + ParsedDeployTokenParams +> { + readonly name = 'deployToken' + + /** Parses mint and metadata params before any RPC. */ + protected override parse(params: GenerateDeployTokenParams): ParsedDeployTokenParams { + validateBaseParams(this.name, params) + validatePreMintParams(this.name, params) + validateMetaplexParams(this.name, params) + return { ...params, config: resolveDeployTokenConfig(params) } + } + + /** Builds the unsigned Solana mint creation instruction set. */ + protected async buildUnsigned( + chain: SolanaChain, + params: ParsedDeployTokenParams, + ): Promise { + const { config } = params + const mint = await PublicKey.createWithSeed(config.payer, config.seed, config.tokenProgram) + const lamports = await chain.connection.getMinimumBalanceForRentExemption(getMintLen([])) + const instructions = createMintInstructions(mint, lamports, params.decimals, config) + + const metadataAddress = params.withMetaplex ? deriveMetadataAddress(mint).toBase58() : undefined + if (params.withMetaplex) + instructions.push( + ...(await createMetadataInstructions( + chain, + mint, + config.payer, + config.tokenProgram, + params.decimals, + config.mintAuthority, + { + name: params.name, + symbol: params.symbol, + uri: params.uri ?? '', + }, + )), + ) + + instructions.push(...createPreMintInstructions(mint, params, config)) + + chain.logger.debug( + `${this.name}: mint = ${mint.toBase58()}, tokenProgram = ${config.tokenProgram.toBase58()}`, + ) + return { + family: ChainFamily.Solana, + instructions, + mainIndex: 0, + tokenAddress: mint.toBase58(), + ...(metadataAddress ? { metadataAddress } : {}), + } + } + + /** Generate, sign, simulate, send, confirm, and return the created mint address. */ + override async execute( + chain: SolanaChain, + params: ExecuteDeployTokenParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + const externalSigner = getExternalMintAuthoritySigner(parsed, parsed.payer) + + if (externalSigner) { + throw new CCTParamsInvalidError( + this.name, + 'mintAuthority', + `requires additional signer: ${externalSigner}. Use generateUnsignedDeployToken and sign externally.`, + ) + } + + const tx = await this.buildUnsigned(chain, parsed) + const hash = await submit(chain, wallet, tx, this.name, computeUnits) + return { + ...hash, + tokenAddress: tx.tokenAddress, + ...(tx.metadataAddress ? { metadataAddress: tx.metadataAddress } : {}), + } + } +} diff --git a/ccip-sdk/src/cct/solana/token/operations/get-token-info.test.ts b/ccip-sdk/src/cct/solana/token/operations/get-token-info.test.ts new file mode 100644 index 000000000..b127eec27 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/operations/get-token-info.test.ts @@ -0,0 +1,86 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { MINT_SIZE, MintLayout, TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { Keypair, PublicKey } from '@solana/web3.js' + +import { CCIPTokenDataParseError } from '../../../../errors/index.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { GetTokenInfo } from './get-token-info.ts' + +const tokenAddress = Keypair.generate().publicKey.toBase58() +const mintAuthority = Keypair.generate().publicKey + +function mintData(): Buffer { + const data = Buffer.alloc(MINT_SIZE) + MintLayout.encode( + { + mintAuthorityOption: 1, + mintAuthority, + supply: 1_000_000n, + decimals: 6, + isInitialized: true, + freezeAuthorityOption: 0, + freezeAuthority: PublicKey.default, + }, + data, + ) + return data +} + +describe('GetTokenInfo (cct/solana)', () => { + describe('query', () => { + it('delegates metadata to SolanaChain.getTokenInfo and reads mint state', async () => { + const metadata = { symbol: 'TKN', decimals: 6, name: 'Token' } + let received: string | undefined + const chain = { + connection: { + getAccountInfo: async () => ({ owner: TOKEN_PROGRAM_ID, data: mintData() }), + }, + getTokenInfo: async (token: string) => { + received = token + return metadata + }, + } as unknown as SolanaChain + + assert.deepEqual(await new GetTokenInfo().query(chain, { tokenAddress }), { + ...metadata, + tokenProgram: TOKEN_PROGRAM_ID.toBase58(), + supply: 1_000_000n, + isInitialized: true, + mintAuthority: mintAuthority.toBase58(), + freezeAuthority: null, + }) + assert.equal(received, tokenAddress) + }) + + it('rejects non-mint SPL accounts before fetching metadata', async () => { + let metadataCalls = 0 + const chain = { + connection: { + getAccountInfo: async () => ({ owner: TOKEN_PROGRAM_ID, data: Buffer.alloc(1) }), + }, + getTokenInfo: async () => { + metadataCalls++ + return { symbol: 'TKN', decimals: 6 } + }, + } as unknown as SolanaChain + + await assert.rejects(new GetTokenInfo().query(chain, { tokenAddress }), (error: unknown) => { + assert.ok(error instanceof CCIPTokenDataParseError) + assert.equal(error.context.token, tokenAddress) + assert.ok(error.cause instanceof Error) + return true + }) + assert.equal(metadataCalls, 0) + }) + }) + + describe('validation', () => { + it('validates the mint address before querying', async () => { + await assert.rejects( + new GetTokenInfo().query({} as SolanaChain, { tokenAddress: 'not-a-public-key' }), + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token/operations/get-token-info.ts b/ccip-sdk/src/cct/solana/token/operations/get-token-info.ts new file mode 100644 index 000000000..70579e7cf --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/operations/get-token-info.ts @@ -0,0 +1,84 @@ +import { unpackMint } from '@solana/spl-token' +import type { PublicKey } from '@solana/web3.js' + +import type { TokenInfo } from '../../../../chain.ts' +import { CCIPTokenDataParseError } from '../../../../errors/index.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { resolveTokenMint } from '../../../../solana/utils.ts' +import { SolanaQuery } from '../../query.ts' +import { parsePublicKey } from '../../validate.ts' + +/** Parameters for reading an SPL token mint's metadata. */ +export type GetTokenInfoParams = { + /** SPL token mint address. */ + tokenAddress: string +} + +/** SPL token metadata and mint state. */ +export type GetTokenInfoResult = TokenInfo & { + /** SPL Token or Token-2022 program that owns the mint. */ + tokenProgram: string + /** Total minted supply in base units. */ + supply: bigint + /** Whether the mint account has been initialized. */ + isInitialized: boolean + /** Authority allowed to mint new tokens, or null for fixed-supply tokens. */ + mintAuthority: string | null + /** Authority allowed to freeze token accounts, or null when freezing is disabled. */ + freezeAuthority: string | null +} + +type ParsedGetTokenInfoParams = GetTokenInfoParams & { mint: PublicKey } + +/** + * Reads an SPL token mint's metadata and state. + * + * @throws {@link CCTParamsInvalidError} If `tokenAddress` is not a valid Solana public key. + * @throws {@link CCIPSplTokenInvalidError} If the token metadata is not a valid SPL token. + * @throws {@link CCIPTokenMintNotFoundError} If the mint account does not exist. + * @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program. + * @throws {@link CCIPTokenDataParseError} If the mint data cannot be parsed. + */ +export class GetTokenInfo extends SolanaQuery< + GetTokenInfoParams, + GetTokenInfoResult, + ParsedGetTokenInfoParams +> { + readonly name = 'getTokenInfo' + + /** Converts and validates the mint address. */ + protected prepare(params: GetTokenInfoParams): ParsedGetTokenInfoParams { + return { + ...params, + mint: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + } + } + + /** Delegates metadata lookup and reads the mint's SPL state. */ + protected async read( + chain: SolanaChain, + { mint, tokenAddress }: ParsedGetTokenInfoParams, + ): Promise { + const account = await resolveTokenMint(chain.connection, mint) + + let state + try { + state = unpackMint(mint, account, account.owner) + } catch (cause) { + throw new CCIPTokenDataParseError(tokenAddress, { + cause: cause instanceof Error ? cause : undefined, + }) + } + + const info = await chain.getTokenInfo(tokenAddress) + + return { + ...info, + tokenProgram: account.owner.toBase58(), + supply: state.supply, + isInitialized: state.isInitialized, + mintAuthority: state.mintAuthority?.toBase58() ?? null, + freezeAuthority: state.freezeAuthority?.toBase58() ?? null, + } + } +} diff --git a/ccip-sdk/src/cct/solana/token/operations/index.ts b/ccip-sdk/src/cct/solana/token/operations/index.ts new file mode 100644 index 000000000..6c8f9477d --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/operations/index.ts @@ -0,0 +1,7 @@ +export * from './approve-token.ts' +export * from './create-token-account.ts' +export * from './deploy-token.ts' +export * from './get-token-info.ts' +export * from './mint-tokens.ts' +export * from './set-token-authority.ts' +export * from './update-metadata-authority.ts' diff --git a/ccip-sdk/src/cct/solana/token/operations/mint-tokens.test.ts b/ccip-sdk/src/cct/solana/token/operations/mint-tokens.test.ts new file mode 100644 index 000000000..af6bace9b --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/operations/mint-tokens.test.ts @@ -0,0 +1,249 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { + TOKEN_2022_PROGRAM_ID, + TOKEN_PROGRAM_ID, + getAssociatedTokenAddressSync, +} from '@solana/spl-token' +import { Keypair, PublicKey } from '@solana/web3.js' + +import { + CCIPTokenAccountNotFoundError, + CCIPTokenMintInvalidError, + CCIPTokenMintNotFoundError, +} from '../../../../errors/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { U64_MAX } from '../../validate.ts' +import { MintTokens } from './mint-tokens.ts' + +const TOKEN = Keypair.generate().publicKey +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const RECIPIENT = Keypair.generate().publicKey +const MULTISIG = Keypair.generate().publicKey.toBase58() +const MULTISIG_SIGNER_1 = Keypair.generate().publicKey.toBase58() +const MULTISIG_SIGNER_2 = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function chain(mintOwner: PublicKey | null = TOKEN_PROGRAM_ID): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + getAccountInfo: async () => + mintOwner ? { owner: mintOwner, data: Buffer.alloc(165) } : null, + }, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(chain(), { + connection: { + getAccountInfo: async () => ({ owner: TOKEN_PROGRAM_ID, data: Buffer.alloc(165) }), + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts: Record = {}, mintOwner?: PublicKey | null) { + return new MintTokens().generate(chain(mintOwner), { + payer: PAYER, + tokenAddress: TOKEN.toBase58(), + recipient: RECIPIENT.toBase58(), + amount: 1_000_000n, + authority: AUTHORITY, + ...opts, + }) +} + +describe('MintTokens (cct/solana)', () => { + describe('generate', () => { + it('mints to the recipient ATA using the detected token program', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + const ata = getAssociatedTokenAddressSync(TOKEN, RECIPIENT, true, TOKEN_PROGRAM_ID) + + assert.ok(instruction) + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(instruction.programId.toBase58(), TOKEN_PROGRAM_ID.toBase58()) + assert.equal(instruction.data[0], 7) // MintTo + assert.equal(instruction.keys[0]!.pubkey.toBase58(), TOKEN.toBase58()) + assert.equal(instruction.keys[1]!.pubkey.toBase58(), ata.toBase58()) + assert.equal(instruction.keys[2]!.pubkey.toBase58(), AUTHORITY) + assert.equal(instruction.data.readBigUInt64LE(1), 1_000_000n) + }) + + it('supports Token-2022 and SPL Token multisig authorities', async () => { + const unsigned = await generate( + { + authority: MULTISIG, + multisigSigners: [MULTISIG_SIGNER_1, MULTISIG_SIGNER_2], + }, + TOKEN_2022_PROGRAM_ID, + ) + + assert.equal(unsigned.instructions[0]!.programId.toBase58(), TOKEN_2022_PROGRAM_ID.toBase58()) + assert.deepEqual( + unsigned.instructions[0]!.keys.slice(2).map(({ pubkey, isSigner }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + })), + [ + { pubkey: MULTISIG, isSigner: false }, + { pubkey: MULTISIG_SIGNER_1, isSigner: true }, + { pubkey: MULTISIG_SIGNER_2, isSigner: true }, + ], + ) + }) + + it('creates a missing recipient ATA when requested', async () => { + const missingAtaChain = { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + getAccountInfo: async (address: PublicKey) => + address.equals(TOKEN) ? { owner: TOKEN_PROGRAM_ID } : null, + }, + } as unknown as SolanaChain + + const unsigned = await new MintTokens().generate(missingAtaChain, { + payer: PAYER, + tokenAddress: TOKEN.toBase58(), + recipient: RECIPIENT.toBase58(), + amount: 1n, + createRecipientATA: true, + }) + + const ata = getAssociatedTokenAddressSync(TOKEN, RECIPIENT, true, TOKEN_PROGRAM_ID) + assert.equal(unsigned.instructions.length, 2) + assert.equal(unsigned.mainIndex, 1) + assert.equal(unsigned.instructions[0]!.data[0], 1) // CreateIdempotent + assert.equal(unsigned.instructions[0]!.keys[1]!.pubkey.toBase58(), ata.toBase58()) + assert.equal(unsigned.instructions[1]!.data[0], 7) // MintTo + assert.equal(unsigned.instructions[1]!.keys[1]!.pubkey.toBase58(), ata.toBase58()) + }) + + it('rejects a missing recipient ATA before simulation', async () => { + const missingAtaChain = { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + getAccountInfo: async (address: PublicKey) => + address.equals(TOKEN) ? { owner: TOKEN_PROGRAM_ID } : null, + }, + } as unknown as SolanaChain + + await assert.rejects( + () => + new MintTokens().generate(missingAtaChain, { + payer: PAYER, + tokenAddress: TOKEN.toBase58(), + recipient: RECIPIENT.toBase58(), + amount: 1n, + }), + (error: unknown) => + error instanceof CCIPTokenAccountNotFoundError && + error.context.token === TOKEN.toBase58() && + error.context.holder === RECIPIENT.toBase58(), + ) + }) + + it('encodes the maximum u64 amount', async () => { + const unsigned = await generate({ amount: U64_MAX }) + assert.equal(unsigned.instructions[0]!.data.readBigUInt64LE(1), U64_MAX) + }) + + it('defaults authority to payer', async () => { + const unsigned = await generate({ authority: undefined }) + assert.equal(unsigned.instructions[0]!.keys[2]!.pubkey.toBase58(), PAYER) + }) + }) + + describe('validation', () => { + it('rejects invalid parameters', async () => { + for (const [opts, param] of [ + [{ tokenAddress: 'invalid' }, 'tokenAddress'], + [{ recipient: 'invalid' }, 'recipient'], + [{ authority: 'invalid' }, 'authority'], + [{ amount: 0n }, 'amount'], + [{ amount: 1 }, 'amount'], + [{ amount: U64_MAX + 1n }, 'amount'], + [{ createRecipientATA: 'invalid' }, 'createRecipientATA'], + [{ multisigSigners: 'invalid' }, 'multisigSigners'], + [{ multisigSigners: ['invalid'] }, 'multisigSigners[0]'], + ] as const) { + await assert.rejects( + () => generate(opts), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + + it('rejects missing and non-token mints', async () => { + await assert.rejects( + () => generate({}, null), + (err: unknown) => err instanceof CCIPTokenMintNotFoundError, + ) + await assert.rejects( + () => generate({}, Keypair.generate().publicKey), + (err: unknown) => err instanceof CCIPTokenMintInvalidError, + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new MintTokens().execute(submitChain(), { + tokenAddress: TOKEN.toBase58(), + recipient: RECIPIENT.toBase58(), + amount: 1n, + wallet: WALLET, + }) + assert.deepEqual(result, { hash: HASH }) + }) + + it('requires unsigned generation for SPL multisig authorities', async () => { + await assert.rejects( + () => + new MintTokens().execute(chain(), { + tokenAddress: TOKEN.toBase58(), + recipient: RECIPIENT.toBase58(), + amount: 1n, + authority: MULTISIG, + multisigSigners: [MULTISIG_SIGNER_1], + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'multisigSigners', + ) + }) + + it('rejects a non-wallet authority for signed minting', async () => { + await assert.rejects( + () => + new MintTokens().execute(chain(), { + tokenAddress: TOKEN.toBase58(), + recipient: RECIPIENT.toBase58(), + amount: 1n, + authority: AUTHORITY, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'mintTokens' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token/operations/mint-tokens.ts b/ccip-sdk/src/cct/solana/token/operations/mint-tokens.ts new file mode 100644 index 000000000..efdf3f2c5 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/operations/mint-tokens.ts @@ -0,0 +1,183 @@ +import { createMintToInstruction } from '@solana/spl-token' +import type { PublicKey, TransactionInstruction } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { resolveATA } from '../../../../solana/utils.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { submit } from '../../submit.ts' +import { + U64_MAX, + parsePublicKey, + resolveExistingTokenAccount, + validateAuthorityMatchesWallet, + validateBigInt, +} from '../../validate.ts' +import { CreateTokenAccount } from './create-token-account.ts' + +type MintTokensParams = { + /** SPL token mint address. */ + tokenAddress: string + /** Recipient owner address; its ATA must exist unless `createRecipientATA` is set. */ + recipient: string + /** + * Amount to mint in base units (not human-readable tokens). + * E.g., 1_000_000n with 6 decimals = 1 token. + * Maximum u64: 2^64 - 1. + */ + amount: bigint + /** Create the recipient ATA idempotently before minting. Defaults to false. */ + createRecipientATA?: boolean + /** Mint authority. Defaults to `payer` for single-signer transactions. */ + authority?: string + /** SPL Token multisig member addresses. Required when authority is an SPL Token multisig. */ + multisigSigners?: string[] +} + +type ParsedMintTokensParams = { + payer: PublicKey + tokenAddress: PublicKey + recipient: PublicKey + amount: bigint + createRecipientATA: boolean + authority: PublicKey + multisigSigners: PublicKey[] +} + +/** Parameters for unsigned Solana SPL token minting. */ +export type GenerateMintTokensParams = SolanaGenerateParams + +/** Unsigned Solana SPL token minting result. */ +export type GenerateMintTokensResult = UnsignedSolanaTx + +/** Parameters for executing Solana SPL token minting. */ +export type ExecuteMintTokensParams = SolanaExecuteParams + +/** Result of executing Solana SPL token minting. */ +export type ExecuteMintTokensResult = TransactionResult + +/** Mints SPL tokens to a recipient's associated token account. */ +export class MintTokens extends SolanaOperation< + MintTokensParams, + UnsignedSolanaTx, + ParsedMintTokensParams +> { + readonly name = 'mintTokens' + + /** Parses public keys, amount, and optional SPL Token multisig signers. */ + protected override parse(params: GenerateMintTokensParams): ParsedMintTokensParams { + validateBigInt(this.name, 'amount', params.amount, 1n, U64_MAX) + if (params.multisigSigners !== undefined && !Array.isArray(params.multisigSigners)) { + throw new CCTParamsInvalidError(this.name, 'multisigSigners', 'must be an array') + } + if (params.createRecipientATA !== undefined && typeof params.createRecipientATA !== 'boolean') { + throw new CCTParamsInvalidError(this.name, 'createRecipientATA', 'must be a boolean') + } + + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + payer, + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + recipient: parsePublicKey(this.name, 'recipient', params.recipient), + amount: params.amount, + createRecipientATA: params.createRecipientATA ?? false, + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + multisigSigners: (params.multisigSigners ?? []).map((signer, i) => + parsePublicKey(this.name, `multisigSigners[${i}]`, signer), + ), + } + } + + /** Builds recipient ATA creation, when requested, followed by an SPL Token `MintTo` instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedMintTokensParams, + ): Promise { + const createRecipientATA = opts.createRecipientATA + ? await new CreateTokenAccount().generate(chain, { + payer: opts.payer.toBase58(), + tokenAddress: opts.tokenAddress.toBase58(), + ownerAddress: opts.recipient.toBase58(), + }) + : undefined + + let tokenAccount: PublicKey + let tokenProgram: PublicKey + + if (opts.createRecipientATA) { + const resolved = await resolveATA(chain.connection, opts.tokenAddress, opts.recipient) + tokenAccount = resolved.ata + tokenProgram = resolved.tokenProgram + } else { + const existing = await resolveExistingTokenAccount( + chain.connection, + opts.tokenAddress, + opts.recipient, + ) + tokenAccount = existing.tokenAccount + tokenProgram = existing.tokenProgram + } + + const instructions: TransactionInstruction[] = [ + ...(createRecipientATA?.instructions ?? []), + createMintToInstruction( + opts.tokenAddress, + tokenAccount, + opts.authority, + opts.amount, + opts.multisigSigners, + tokenProgram, + ), + ] + + chain.logger.debug( + `${ + this.name + }: token = ${opts.tokenAddress.toBase58()}, recipient = ${opts.recipient.toBase58()}, amount = ${ + opts.amount + }`, + ) + return { + family: ChainFamily.Solana, + instructions, + mainIndex: createRecipientATA ? 1 : 0, + } + } + + /** Generate, sign, simulate, send, and confirm with the mint authority wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteMintTokensParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (parsed.multisigSigners.length > 0) { + throw new CCTParamsInvalidError( + this.name, + 'multisigSigners', + 'requires externally signed transactions; use generateUnsignedMintTokens', + ) + } + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'mintTokens requires authority to be the executing wallet. Use generateUnsignedMintTokens for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token/operations/set-token-authority.test.ts b/ccip-sdk/src/cct/solana/token/operations/set-token-authority.test.ts new file mode 100644 index 000000000..850a29fe6 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/operations/set-token-authority.test.ts @@ -0,0 +1,241 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { Keypair, PublicKey } from '@solana/web3.js' + +import { CCIPTokenMintInvalidError, CCIPTokenMintNotFoundError } from '../../../../errors/index.ts' +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import { SetTokenAuthority } from './set-token-authority.ts' + +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const NEW_AUTHORITY = Keypair.generate().publicKey.toBase58() +const MULTISIG = Keypair.generate().publicKey.toBase58() +const MULTISIG_SIGNER_1 = Keypair.generate().publicKey.toBase58() +const MULTISIG_SIGNER_2 = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: Keypair.generate().publicKey, + signTransaction: async (tx: T) => tx, +} + +function chain(mintOwner: PublicKey | null = TOKEN_PROGRAM_ID): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + getAccountInfo: async () => (mintOwner ? { owner: mintOwner } : null), + }, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return Object.assign(chain(), { + connection: { + getAccountInfo: async () => ({ owner: TOKEN_PROGRAM_ID }), + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + }) +} + +function generate(opts: Record = {}, mintOwner?: PublicKey | null) { + return new SetTokenAuthority().generate(chain(mintOwner), { + tokenAddress: TOKEN, + payer: PAYER, + authority: AUTHORITY, + newAuthority: NEW_AUTHORITY, + authorityTypes: ['mint', 'freeze'], + ...opts, + }) +} + +describe('SetTokenAuthority (cct/solana)', () => { + describe('generate', () => { + it('builds selected mint and freeze authority updates', async () => { + const unsigned = await generate() + + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(unsigned.instructions.length, 2) + assert.deepEqual( + unsigned.instructions.map((instruction) => ({ + programId: instruction.programId.toBase58(), + authorityType: instruction.data[1], + mint: instruction.keys[0]!.pubkey.toBase58(), + authority: instruction.keys[1]!.pubkey.toBase58(), + newAuthority: instruction.data.subarray(3).toString('hex'), + })), + [ + { + programId: TOKEN_PROGRAM_ID.toBase58(), + authorityType: 0, // MintTokens + mint: TOKEN, + authority: AUTHORITY, + newAuthority: new PublicKey(NEW_AUTHORITY).toBuffer().toString('hex'), + }, + { + programId: TOKEN_PROGRAM_ID.toBase58(), + authorityType: 1, // FreezeAccount + mint: TOKEN, + authority: AUTHORITY, + newAuthority: new PublicKey(NEW_AUTHORITY).toBuffer().toString('hex'), + }, + ], + ) + }) + + it('builds only the selected authority update for Token-2022', async () => { + const unsigned = await generate({ authorityTypes: ['freeze'] }, TOKEN_2022_PROGRAM_ID) + + assert.equal(unsigned.instructions.length, 1) + assert.equal(unsigned.instructions[0]!.programId.toBase58(), TOKEN_2022_PROGRAM_ID.toBase58()) + assert.equal(unsigned.instructions[0]!.data[1], 1) // FreezeAccount + }) + + it('includes SPL multisig member signers', async () => { + const unsigned = await generate({ + authority: MULTISIG, + authorityTypes: ['mint'], + multisigSigners: [MULTISIG_SIGNER_1, MULTISIG_SIGNER_2], + }) + + assert.deepEqual( + unsigned.instructions[0]!.keys.map(({ pubkey, isSigner, isWritable }) => ({ + pubkey: pubkey.toBase58(), + isSigner, + isWritable, + })), + [ + { pubkey: TOKEN, isSigner: false, isWritable: true }, + { pubkey: MULTISIG, isSigner: false, isWritable: false }, + { pubkey: MULTISIG_SIGNER_1, isSigner: true, isWritable: false }, + { pubkey: MULTISIG_SIGNER_2, isSigner: true, isWritable: false }, + ], + ) + }) + + it('builds authority revocation with a null new authority', async () => { + const unsigned = await generate({ authorityTypes: ['mint'], newAuthority: null }) + const [instruction] = unsigned.instructions + + assert.ok(instruction) + assert.equal(instruction.data[0], 6) // SetAuthority + assert.equal(instruction.data[1], 0) // MintTokens + assert.equal(instruction.data[2], 0) // COption::None + assert.equal(instruction.data.length, 3) + }) + + it('defaults authority to payer', async () => { + const unsigned = await generate({ authority: undefined }) + + assert.equal(unsigned.instructions[0]!.keys[1]!.pubkey.toBase58(), PAYER) + }) + }) + + describe('validation', () => { + it('rejects invalid public keys', async () => { + for (const param of ['tokenAddress', 'newAuthority', 'authority']) { + await assert.rejects( + () => generate({ [param]: 'invalid' }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + + it('rejects invalid multisig signers', async () => { + for (const [multisigSigners, param] of [ + ['invalid', 'multisigSigners'], + [['invalid'], 'multisigSigners[0]'], + ]) { + await assert.rejects( + () => generate({ multisigSigners }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + + it('reports invalid authority role selections', async () => { + const cases: [unknown, string][] = [ + [undefined, 'must be an array'], + [[], 'must not be empty'], + [['mint', 'mint'], 'must not contain duplicates'], + [['close'], 'must contain only mint and/or freeze'], + ] + for (const [authorityTypes, message] of cases) { + await assert.rejects( + () => generate({ authorityTypes }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'authorityTypes' && + err.message.includes(message), + ) + } + }) + + it('rejects missing and non-token mints', async () => { + await assert.rejects( + () => generate({}, null), + (err: unknown) => err instanceof CCIPTokenMintNotFoundError, + ) + await assert.rejects( + () => generate({}, Keypair.generate().publicKey), + (err: unknown) => err instanceof CCIPTokenMintInvalidError, + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new SetTokenAuthority().execute(submitChain(), { + tokenAddress: TOKEN, + newAuthority: NEW_AUTHORITY, + authorityTypes: ['mint'], + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('requires unsigned generation for SPL multisig authorities', async () => { + await assert.rejects( + () => + new SetTokenAuthority().execute(chain(), { + tokenAddress: TOKEN, + newAuthority: NEW_AUTHORITY, + authority: MULTISIG, + authorityTypes: ['mint'], + multisigSigners: [MULTISIG_SIGNER_1], + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'multisigSigners', + ) + }) + + it('rejects a non-wallet authority for signed updates', async () => { + await assert.rejects( + () => + new SetTokenAuthority().execute(chain(), { + tokenAddress: TOKEN, + newAuthority: NEW_AUTHORITY, + authority: AUTHORITY, + authorityTypes: ['mint'], + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'setTokenAuthority' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token/operations/set-token-authority.ts b/ccip-sdk/src/cct/solana/token/operations/set-token-authority.ts new file mode 100644 index 000000000..2a5220712 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/operations/set-token-authority.ts @@ -0,0 +1,175 @@ +import { AuthorityType, createSetAuthorityInstruction } from '@solana/spl-token' +import type { PublicKey, TransactionInstruction } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { resolveTokenProgram } from '../../../../solana/utils.ts' +import { CCTParamsInvalidError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { submit } from '../../submit.ts' +import { parsePublicKey, validateAuthorityMatchesWallet } from '../../validate.ts' +import { TOKEN_AUTHORITY_TYPES } from '../constants.ts' + +/** SPL Token authority role that can be set. */ +export type TokenAuthorityType = (typeof TOKEN_AUTHORITY_TYPES)[keyof typeof TOKEN_AUTHORITY_TYPES] + +type SetTokenAuthorityParams = { + /** SPL token mint address. */ + tokenAddress: string + /** Address to receive the selected authority roles, or **null to permanently revoke** them. ⚠️ Revocation is irreversible. */ + newAuthority: string | null + /** Current authority. Defaults to `payer` for single-signer transactions. */ + authority?: string + /** SPL Token multisig member addresses. Required when authority is an SPL Token multisig. */ + multisigSigners?: string[] + /** + * Authority roles to set. Specify `['mint']`, `['freeze']`, or `['mint', 'freeze']`. + * The same new authority or revocation applies to every selected role. To set roles to different + * authorities, make separate calls. + */ + authorityTypes: TokenAuthorityType[] +} + +type ParsedSetTokenAuthorityParams = { + tokenAddress: PublicKey + newAuthority: PublicKey | null + authority: PublicKey + multisigSigners: PublicKey[] + authorityTypes: TokenAuthorityType[] +} + +/** Parameters for unsigned Solana SPL Token authority update. */ +export type GenerateSetTokenAuthorityParams = SolanaGenerateParams + +/** Unsigned Solana SPL Token authority update result. */ +export type GenerateSetTokenAuthorityResult = UnsignedSolanaTx + +/** Parameters for executing Solana SPL Token authority update. */ +export type ExecuteSetTokenAuthorityParams = SolanaExecuteParams + +/** Result of executing Solana SPL Token authority update. */ +export type ExecuteSetTokenAuthorityResult = TransactionResult + +const SPL_AUTHORITY_TYPES: Record = { + mint: AuthorityType.MintTokens, + freeze: AuthorityType.FreezeAccount, +} + +/** + * Immediately sets mint authority, freeze authority, or both for an SPL Token mint; there is no + * propose-and-accept step. + * + * @remarks + * ⚠️ **IRREVERSIBLE:** Setting `newAuthority` to null permanently revokes the selected roles. + * Once revoked, a revoked mint or freeze authority **cannot be recovered, transferred, or restored**. + * + * Once confirmed, the current authority loses the selected roles. All selected roles must have the + * same current authority. Supply `multisigSigners` when that authority is an SPL Token multisig. + * **Atomic:** All selected roles update in one transaction. If any selected update fails, none are + * committed. + */ +export class SetTokenAuthority extends SolanaOperation< + SetTokenAuthorityParams, + UnsignedSolanaTx, + ParsedSetTokenAuthorityParams +> { + readonly name = 'setTokenAuthority' + + /** Parses public keys and validates the selected authority roles. */ + protected override parse(params: GenerateSetTokenAuthorityParams): ParsedSetTokenAuthorityParams { + const authorityTypes = params.authorityTypes + if (!Array.isArray(authorityTypes)) { + throw new CCTParamsInvalidError(this.name, 'authorityTypes', 'must be an array') + } + if (authorityTypes.length === 0) { + throw new CCTParamsInvalidError(this.name, 'authorityTypes', 'must not be empty') + } + if (new Set(authorityTypes).size !== authorityTypes.length) { + throw new CCTParamsInvalidError(this.name, 'authorityTypes', 'must not contain duplicates') + } + if (authorityTypes.some((type) => !Object.values(TOKEN_AUTHORITY_TYPES).includes(type))) { + throw new CCTParamsInvalidError( + this.name, + 'authorityTypes', + 'must contain only mint and/or freeze', + ) + } + + if (params.multisigSigners !== undefined && !Array.isArray(params.multisigSigners)) { + throw new CCTParamsInvalidError(this.name, 'multisigSigners', 'must be an array') + } + + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + newAuthority: + params.newAuthority === null + ? null + : parsePublicKey(this.name, 'newAuthority', params.newAuthority), + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + multisigSigners: (params.multisigSigners ?? []).map((signer, i) => + parsePublicKey(this.name, `multisigSigners[${i}]`, signer), + ), + authorityTypes, + } + } + + /** Builds one SPL Token `SetAuthority` instruction for each selected authority role. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedSetTokenAuthorityParams, + ): Promise { + const tokenProgram = await resolveTokenProgram(chain.connection, opts.tokenAddress) + const instructions: TransactionInstruction[] = opts.authorityTypes.map((authorityType) => + createSetAuthorityInstruction( + opts.tokenAddress, + opts.authority, + SPL_AUTHORITY_TYPES[authorityType], + opts.newAuthority, + opts.multisigSigners, + tokenProgram, + ), + ) + + chain.logger.debug( + `${this.name}: token = ${opts.tokenAddress.toBase58()}, authorityTypes = ${opts.authorityTypes.join(',')}, newAuthority = ${opts.newAuthority?.toBase58() ?? 'revoked'}`, + ) + return { family: ChainFamily.Solana, instructions, mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the current authority wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteSetTokenAuthorityParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (parsed.multisigSigners.length > 0) { + throw new CCTParamsInvalidError( + this.name, + 'multisigSigners', + 'requires externally signed transactions; use generateUnsignedSetTokenAuthority', + ) + } + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'setTokenAuthority requires authority to be the executing wallet. Use generateUnsignedSetTokenAuthority for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/token/operations/update-metadata-authority.test.ts b/ccip-sdk/src/cct/solana/token/operations/update-metadata-authority.test.ts new file mode 100644 index 000000000..4502ea3ae --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/operations/update-metadata-authority.test.ts @@ -0,0 +1,174 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { Keypair, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import { CCTParamsInvalidError, CCTTxFailedError } from '../../../errors.ts' +import { UpdateMetadataAuthority } from './update-metadata-authority.ts' + +const METAPLEX_PROGRAM_ID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s') +const TOKEN = Keypair.generate().publicKey.toBase58() +const PAYER = Keypair.generate().publicKey.toBase58() +const AUTHORITY = Keypair.generate().publicKey.toBase58() +const NEW_AUTHORITY = Keypair.generate().publicKey.toBase58() +const HASH = Keypair.generate().publicKey.toBase58() +const WALLET = { + publicKey: new PublicKey(AUTHORITY), + signTransaction: async (tx: T) => tx, +} + +function metadataData(authority = AUTHORITY, isMutable = true, mint = TOKEN): Buffer { + return Buffer.concat([ + Buffer.from([4]), // MetadataV1 + new PublicKey(authority).toBuffer(), + new PublicKey(mint).toBuffer(), + Buffer.alloc(12), // Empty name, symbol, and URI strings. + Buffer.alloc(2), // Seller fee basis points. + Buffer.from([0, 0, isMutable ? 1 : 0, 0, 0, 0, 0, 0]), + ]) +} + +function metadataAccount(metadata = metadataData()) { + return { + data: metadata, + executable: false, + lamports: 0, + owner: METAPLEX_PROGRAM_ID, + rentEpoch: 0, + } +} + +function chain(metadata: Buffer | null = metadataData()): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + rpcEndpoint: 'http://localhost:8899', + getAccountInfo: async () => (metadata ? metadataAccount(metadata) : null), + }, + } as unknown as SolanaChain +} + +function submitChain(): SolanaChain { + return { + logger: { debug() {}, info() {}, warn() {}, error() {} }, + connection: { + rpcEndpoint: 'http://localhost:8899', + getAccountInfo: async () => metadataAccount(), + simulateTransaction: async () => ({ value: { err: null, logs: [], unitsConsumed: 1 } }), + getLatestBlockhash: async () => ({ + blockhash: PublicKey.default.toBase58(), + lastValidBlockHeight: 1, + }), + sendTransaction: async () => HASH, + confirmTransaction: async () => ({ value: { err: null } }), + }, + } as unknown as SolanaChain +} + +function generate(opts: Record = {}, metadata?: Buffer | null) { + return new UpdateMetadataAuthority().generate(chain(metadata), { + tokenAddress: TOKEN, + payer: PAYER, + authority: AUTHORITY, + newAuthority: NEW_AUTHORITY, + ...opts, + }) +} + +describe('UpdateMetadataAuthority (cct/solana)', () => { + describe('generate', () => { + it('builds a Metaplex UpdateV1 instruction', async () => { + const unsigned = await generate() + const [instruction] = unsigned.instructions + + assert.ok(instruction) + assert.equal(unsigned.family, ChainFamily.Solana) + assert.equal(unsigned.mainIndex, 0) + assert.equal(unsigned.instructions.length, 1) + assert.equal(instruction.programId.toBase58(), METAPLEX_PROGRAM_ID.toBase58()) + assert.equal(instruction.data[0], 50) // Update + assert.equal(instruction.data[1], 0) // UpdateV1 + assert.equal(instruction.keys[0]!.pubkey.toBase58(), AUTHORITY) + assert.equal(instruction.keys[0]!.isSigner, true) + }) + + it('defaults authority to payer', async () => { + const unsigned = await generate({ authority: undefined }, metadataData(PAYER)) + + assert.equal(unsigned.instructions[0]!.keys[0]!.pubkey.toBase58(), PAYER) + }) + }) + + describe('validation', () => { + it('rejects invalid public keys before RPC', async () => { + for (const param of ['tokenAddress', 'newAuthority', 'authority']) { + await assert.rejects( + () => generate({ [param]: 'invalid' }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + + it('requires Metaplex metadata with the current authority', async () => { + for (const [metadata, param] of [ + [null, 'tokenAddress'], + [metadataData(AUTHORITY, true, PAYER), 'tokenAddress'], + [metadataData(PAYER), 'authority'], + [Buffer.alloc(0), 'tokenAddress'], + ] as const) { + await assert.rejects( + () => generate({}, metadata), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === param, + ) + } + }) + + it('reports the supplied and current authority on mismatch', async () => { + await assert.rejects( + () => generate({}, metadataData(PAYER)), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.message.includes(AUTHORITY) && + err.message.includes(PAYER), + ) + }) + + it('rejects immutable metadata before submission', async () => { + await assert.rejects( + () => generate({}, metadataData(AUTHORITY, false)), + (err: unknown) => + err instanceof CCTTxFailedError && err.message.includes('metadata is immutable'), + ) + }) + }) + + describe('execute', () => { + it('signs, submits, and returns the tx hash', async () => { + const result = await new UpdateMetadataAuthority().execute(submitChain(), { + tokenAddress: TOKEN, + newAuthority: NEW_AUTHORITY, + wallet: WALLET, + }) + + assert.deepEqual(result, { hash: HASH }) + }) + + it('rejects a non-wallet authority for signed updates', async () => { + await assert.rejects( + () => + new UpdateMetadataAuthority().execute(chain(), { + tokenAddress: TOKEN, + newAuthority: NEW_AUTHORITY, + authority: PAYER, + wallet: WALLET, + }), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'updateMetadataAuthority' && + err.context.param === 'authority', + ) + }) + }) +}) diff --git a/ccip-sdk/src/cct/solana/token/operations/update-metadata-authority.ts b/ccip-sdk/src/cct/solana/token/operations/update-metadata-authority.ts new file mode 100644 index 000000000..b33b77388 --- /dev/null +++ b/ccip-sdk/src/cct/solana/token/operations/update-metadata-authority.ts @@ -0,0 +1,203 @@ +import type { MetadataAccountData } from '@metaplex-foundation/mpl-token-metadata' +import { type TransactionInstruction, PublicKey } from '@solana/web3.js' + +import { ChainFamily } from '../../../../networks.ts' +import type { SolanaChain } from '../../../../solana/index.ts' +import type { UnsignedSolanaTx } from '../../../../solana/types.ts' +import { CCTParamsInvalidError, CCTTxFailedError } from '../../../errors.ts' +import type { TransactionResult } from '../../../operation.ts' +import { + type SolanaExecuteParams, + type SolanaGenerateParams, + SolanaOperation, +} from '../../operation.ts' +import { deriveMetadataAddress } from '../../programs/token.ts' +import { submit } from '../../submit.ts' +import { parsePublicKey, validateAuthorityMatchesWallet } from '../../validate.ts' +import { METADATA_PROGRAM_ID } from '../constants.ts' + +type UpdateMetadataAuthorityParams = { + /** SPL token mint address with Metaplex metadata. */ + tokenAddress: string + /** Address to receive the Metaplex metadata update authority. */ + newAuthority: string + /** Current metadata update authority. Defaults to `payer` for single-signer transactions. */ + authority?: string +} + +type ParsedUpdateMetadataAuthorityParams = { + tokenAddress: PublicKey + newAuthority: PublicKey + authority: PublicKey + payer: PublicKey +} + +function validateMetadataAuthority( + operation: string, + metadata: MetadataAccountData, + { authority }: ParsedUpdateMetadataAuthorityParams, +): void { + if (!new PublicKey(metadata.updateAuthority).equals(authority)) { + throw new CCTParamsInvalidError( + operation, + 'authority', + `${authority.toBase58()} is not the current metadata update authority (${ + metadata.updateAuthority + })`, + ) + } + if (!metadata.isMutable) { + throw new CCTTxFailedError(operation, 'metadata is immutable and cannot be updated') + } +} + +/** Parameters for unsigned Solana Metaplex metadata authority update. */ +export type GenerateUpdateMetadataAuthorityParams = + SolanaGenerateParams + +/** Unsigned Solana Metaplex metadata authority update result. */ +export type GenerateUpdateMetadataAuthorityResult = UnsignedSolanaTx + +/** Parameters for executing Solana Metaplex metadata authority update. */ +export type ExecuteUpdateMetadataAuthorityParams = + SolanaExecuteParams + +/** Result of executing Solana Metaplex metadata authority update. */ +export type ExecuteUpdateMetadataAuthorityResult = TransactionResult + +async function loadMetaplex() { + const [metadata, umi, bundleDefaults, web3] = await Promise.all([ + import('@metaplex-foundation/mpl-token-metadata'), + import('@metaplex-foundation/umi'), + import('@metaplex-foundation/umi-bundle-defaults'), + import('@metaplex-foundation/umi-web3js-adapters'), + ]) + + return { + createNoopSigner: umi.createNoopSigner, + createUmi: bundleDefaults.createUmi, + getMetadataAccountDataSerializer: metadata.getMetadataAccountDataSerializer, + mplTokenMetadata: metadata.mplTokenMetadata, + publicKey: umi.publicKey, + signerIdentity: umi.signerIdentity, + toWeb3JsInstruction: web3.toWeb3JsInstruction, + updateV1: metadata.updateV1, + } +} + +async function getMetadata( + operation: string, + chain: SolanaChain, + tokenAddress: PublicKey, + metaplex: Awaited>, +): Promise { + const metadata = await chain.connection.getAccountInfo(deriveMetadataAddress(tokenAddress)) + if (!metadata || !metadata.owner.equals(METADATA_PROGRAM_ID)) { + throw new CCTParamsInvalidError( + operation, + 'tokenAddress', + 'mint not found or does not have Metaplex metadata', + ) + } + + let parsed: MetadataAccountData + try { + parsed = metaplex.getMetadataAccountDataSerializer().deserialize(metadata.data)[0] + } catch { + throw new CCTParamsInvalidError( + operation, + 'tokenAddress', + 'mint not found or does not have Metaplex metadata', + ) + } + if (!new PublicKey(parsed.mint).equals(tokenAddress)) { + throw new CCTParamsInvalidError( + operation, + 'tokenAddress', + 'mint not found or does not have Metaplex metadata', + ) + } + return parsed +} + +/** Transfers the Metaplex metadata update authority for an SPL token mint. */ +export class UpdateMetadataAuthority extends SolanaOperation< + UpdateMetadataAuthorityParams, + UnsignedSolanaTx, + ParsedUpdateMetadataAuthorityParams +> { + readonly name = 'updateMetadataAuthority' + + /** Parses the mint and current and new metadata update authorities. */ + protected override parse( + params: GenerateUpdateMetadataAuthorityParams, + ): ParsedUpdateMetadataAuthorityParams { + const payer = parsePublicKey(this.name, 'payer', params.payer) + return { + tokenAddress: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress), + newAuthority: parsePublicKey(this.name, 'newAuthority', params.newAuthority), + authority: + params.authority === undefined + ? payer + : parsePublicKey(this.name, 'authority', params.authority), + payer, + } + } + + /** Validates the Metaplex metadata account and builds its `UpdateV1` instruction. */ + protected async buildUnsigned( + chain: SolanaChain, + opts: ParsedUpdateMetadataAuthorityParams, + ): Promise { + const metaplex = await loadMetaplex() + const authority = metaplex.createNoopSigner(metaplex.publicKey(opts.authority.toBase58())) + const umi = metaplex + .createUmi(chain.connection) + .use(metaplex.mplTokenMetadata()) + .use( + metaplex.signerIdentity( + metaplex.createNoopSigner(metaplex.publicKey(opts.payer.toBase58())), + ), + ) + + const metadata = await getMetadata(this.name, chain, opts.tokenAddress, metaplex) + validateMetadataAuthority(this.name, metadata, opts) + + const mint = metaplex.publicKey(opts.tokenAddress.toBase58()) + + const instructions: TransactionInstruction[] = metaplex + .updateV1(umi, { + mint, + authority, + newUpdateAuthority: metaplex.publicKey(opts.newAuthority.toBase58()), + }) + .getInstructions() + .map(metaplex.toWeb3JsInstruction) + + chain.logger.debug( + `${ + this.name + }: token = ${opts.tokenAddress.toBase58()}, newAuthority = ${opts.newAuthority.toBase58()}`, + ) + return { family: ChainFamily.Solana, instructions, mainIndex: 0 } + } + + /** Generate, sign, simulate, send, and confirm with the current metadata authority wallet. */ + override async execute( + chain: SolanaChain, + params: ExecuteUpdateMetadataAuthorityParams, + ): Promise { + const { wallet, computeUnits, parsed } = this.prepareWalletExecution(params) + + if (params.authority !== undefined) { + validateAuthorityMatchesWallet( + this.name, + parsed.authority, + wallet.publicKey, + 'updateMetadataAuthority requires authority to be the executing wallet. Use generateUnsignedUpdateMetadataAuthority for externally signed transactions.', + ) + } + + return submit(chain, wallet, await this.buildUnsigned(chain, parsed), this.name, computeUnits) + } +} diff --git a/ccip-sdk/src/cct/solana/validate.test.ts b/ccip-sdk/src/cct/solana/validate.test.ts new file mode 100644 index 000000000..40c8f5276 --- /dev/null +++ b/ccip-sdk/src/cct/solana/validate.test.ts @@ -0,0 +1,354 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +import { MINT_SIZE, MintLayout, TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' + +import { CCIPTokenAccountNotFoundError } from '../../errors/index.ts' +import { CCTParamsInvalidError, CCTTxFailedError } from '../errors.ts' +import { type PoolProgramRef, TOKEN_POOL_PROGRAMS } from './programs/token-pool.ts' +import { + parseHexBytes, + parseNonEmptyHexBytes, + parsePublicKey, + resolveExistingTokenAccount, + resolveLockReleasePoolProgram, + resolvePoolProgram, + validateAuthorityMatchesWallet, + validateBigInt, + validateDelegation, + validateInteger, + validateNonEmptyString, + validateOptionalPublicKey, + validatePoolType, + validatePublicKey, + validatePublicKeys, + validateUniqueChainSelectors, + validateUniqueHexBytes, + validateUniquePublicKeys, + validateWritableIndexes, +} from './validate.ts' + +function mintData() { + const data = Buffer.alloc(MINT_SIZE) + MintLayout.encode( + { + mintAuthorityOption: 1, + mintAuthority: PublicKey.default, + supply: 0n, + decimals: 6, + isInitialized: true, + freezeAuthorityOption: 0, + freezeAuthority: PublicKey.default, + }, + data, + ) + return data +} + +describe('Validate (cct/solana)', () => { + it('parses valid public keys', () => { + const key = parsePublicKey('op', 'payer', PublicKey.default.toBase58()) + assert.ok(key.equals(PublicKey.default)) + }) + + it('parses hex bytes with an optional maximum size', () => { + assert.deepEqual(parseHexBytes('op', 'address', '0x01ab', 2), Buffer.from('01ab', 'hex')) + assert.deepEqual(parseHexBytes('op', 'address', ''), Buffer.alloc(0)) + assert.throws( + () => parseHexBytes('op', 'address', '0x123', 2), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.reason === 'must be a hex string of at most 2 bytes', + ) + assert.throws(() => parseHexBytes('op', 'address', null), CCTParamsInvalidError) + }) + + it('rejects empty hex bytes when required', () => { + assert.deepEqual(parseNonEmptyHexBytes('op', 'address', '0x01'), Buffer.from([1])) + assert.throws( + () => parseNonEmptyHexBytes('op', 'address', ''), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.reason === 'must not be empty', + ) + }) + + it('accepts valid public keys', () => { + assert.doesNotThrow(() => validatePublicKey('op', 'payer', PublicKey.default.toBase58())) + }) + + it('accepts omitted and valid optional public keys', () => { + assert.doesNotThrow(() => validateOptionalPublicKey('op', 'authority', undefined)) + assert.doesNotThrow(() => + validateOptionalPublicKey('op', 'authority', PublicKey.default.toBase58()), + ) + }) + + it('rejects invalid optional public keys', () => { + for (const value of [null, '']) { + assert.throws( + () => validateOptionalPublicKey('op', 'authority', value), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'authority', + ) + } + }) + + it('rejects non-string public keys', () => { + assert.throws( + () => validatePublicKey('op', 'payer', 123), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'op' && + err.context.param === 'payer', + ) + }) + + it('rejects invalid public key strings', () => { + assert.throws( + () => validatePublicKey('op', 'payer', 'nope'), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'op' && + err.context.param === 'payer', + ) + }) + + it('validates public key arrays', () => { + assert.doesNotThrow(() => validatePublicKeys('op', 'signers', [])) + assert.doesNotThrow(() => validatePublicKeys('op', 'signers', [PublicKey.default.toBase58()])) + assert.throws( + () => validatePublicKeys('op', 'signers', ['nope']), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'signers[0]', + ) + assert.throws( + () => validatePublicKeys('op', 'signers', 'nope'), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'signers', + ) + }) + + it('validates non-empty strings', () => { + assert.doesNotThrow(() => validateNonEmptyString('op', 'seed', 'abc')) + assert.throws( + () => validateNonEmptyString('op', 'seed', ' '), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'seed', + ) + }) + + it('validates the executing authority', () => { + const authority = PublicKey.default + + assert.doesNotThrow(() => validateAuthorityMatchesWallet('op', authority, authority)) + assert.throws( + () => + validateAuthorityMatchesWallet( + 'op', + authority, + new PublicKey(Uint8Array.from({ length: 32 }, () => 1)), + ), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'authority', + ) + }) + + it('validates pool types', () => { + assert.doesNotThrow(() => validatePoolType('op', 'poolType', 'burn-mint')) + assert.doesNotThrow(() => validatePoolType('op', 'poolType', 'lock-release')) + assert.throws( + () => validatePoolType('op', 'poolType', 'nope'), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'poolType', + ) + }) + + it('resolves pool programs', () => { + assert.equal( + resolvePoolProgram('op', { poolType: 'burn-mint' }).toBase58(), + TOKEN_POOL_PROGRAMS['burn-mint'], + ) + assert.ok( + resolvePoolProgram('op', { poolProgramAddress: PublicKey.default.toBase58() }).equals( + PublicKey.default, + ), + ) + + const invalidRefs: unknown[] = [ + {}, + { poolType: 'burn-mint', poolProgramAddress: PublicKey.default.toBase58() }, + { poolType: 'nope' }, + { poolProgramAddress: 'nope' }, + ] + for (const params of invalidRefs) { + assert.throws(() => resolvePoolProgram('op', params as PoolProgramRef), CCTParamsInvalidError) + } + }) + + it('resolves pool references with the other key explicitly undefined', () => { + // Value semantics: an explicitly-set `undefined` key must not count as provided. + const custom = PublicKey.default.toBase58() + + assert.equal( + resolvePoolProgram('op', { poolProgramAddress: custom, poolType: undefined }).toBase58(), + custom, + ) + assert.equal( + resolvePoolProgram('op', { + poolType: 'burn-mint', + poolProgramAddress: undefined, + }).toBase58(), + TOKEN_POOL_PROGRAMS['burn-mint'], + ) + }) + + it('resolves lock-release pool programs only', () => { + assert.ok(resolveLockReleasePoolProgram('op', { poolType: 'lock-release' })) + assert.throws( + () => resolveLockReleasePoolProgram('op', { poolType: 'burn-mint' }), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'poolType', + ) + }) + + it('validates integers', () => { + assert.doesNotThrow(() => validateInteger('op', 'threshold', 1)) + assert.doesNotThrow(() => validateInteger('op', 'decimals', 255, 0, 255)) + assert.throws( + () => validateInteger('op', 'decimals', 256, 0, 255), + (err: unknown) => err instanceof CCTParamsInvalidError && err.context.param === 'decimals', + ) + assert.throws(() => validateInteger('op', 'threshold', 0, 1), CCTParamsInvalidError) + assert.throws(() => validateInteger('op', 'limit', 2, undefined, 1), CCTParamsInvalidError) + assert.throws(() => validateInteger('op', 'integer', 1.5), CCTParamsInvalidError) + }) + + it('validates bigint bounds with useful errors', () => { + assert.doesNotThrow(() => validateBigInt('op', 'selector', 0n, 0n)) + assert.throws( + () => validateBigInt('op', 'selector', -1n, 0n), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.reason === 'must be a bigint >= 0', + ) + assert.throws( + () => validateBigInt('op', 'selector', 2n, undefined, 1n), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.reason === 'must be a bigint <= 1', + ) + }) + + it('rejects duplicate public keys', () => { + const address = PublicKey.default + assert.throws( + () => validateUniquePublicKeys('op', 'addresses', [address, address]), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'addresses[1]', + ) + }) + + it('rejects duplicate chain selectors', () => { + assert.doesNotThrow(() => validateUniqueChainSelectors('op', 'selectors', [1n, 2n])) + assert.throws( + () => validateUniqueChainSelectors('op', 'selectors', [1n, 1n]), + (err: unknown) => + err instanceof CCTParamsInvalidError && err.context.param === 'selectors[1]', + ) + }) + + it('rejects duplicate hex byte values', () => { + assert.doesNotThrow(() => validateUniqueHexBytes('op', 'addresses', [Buffer.from('01', 'hex')])) + assert.throws( + () => + validateUniqueHexBytes('op', 'addresses', [ + Buffer.from('01', 'hex'), + Buffer.from('01', 'hex'), + ]), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.param === 'addresses[1]' && + err.context.reason === 'must not contain duplicate hex values', + ) + assert.throws( + () => + validateUniqueHexBytes( + 'op', + 'remotePoolAddresses', + [Buffer.from('01', 'hex'), Buffer.from('01', 'hex')], + 'remote pool addresses', + ), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.reason === 'must not contain duplicate remote pool addresses', + ) + }) + + it('validates token delegation', () => { + const tokenAccount = PublicKey.default + const delegate = new PublicKey(Uint8Array.from({ length: 32 }, () => 1)) + const otherDelegate = new PublicKey(Uint8Array.from({ length: 32 }, () => 2)) + + assert.doesNotThrow(() => + validateDelegation( + 'op', + tokenAccount, + { delegate, delegatedAmount: 2n } as never, + delegate, + 2n, + ), + ) + for (const account of [ + { delegate: null, delegatedAmount: 2n }, + { delegate: otherDelegate, delegatedAmount: 2n }, + { delegate, delegatedAmount: 1n }, + ]) { + assert.throws( + () => validateDelegation('op', tokenAccount, account as never, delegate, 2n), + (err: unknown) => err instanceof CCTTxFailedError, + ) + } + }) + + it('maps missing token accounts and preserves other lookup errors', async () => { + const mint = new PublicKey(Uint8Array.from({ length: 32 }, () => 1)) + const holder = new PublicKey(Uint8Array.from({ length: 32 }, () => 2)) + const tokenAccount = new PublicKey(Uint8Array.from({ length: 32 }, () => 3)) + const connection = { + getAccountInfo: async (address: PublicKey) => + address.equals(mint) ? { owner: TOKEN_PROGRAM_ID, data: mintData() } : null, + } + + await assert.rejects( + () => resolveExistingTokenAccount(connection as never, mint, holder, tokenAccount), + (err: unknown) => err instanceof CCIPTokenAccountNotFoundError, + ) + + const invalidConnection = { + getAccountInfo: async (address: PublicKey) => + address.equals(mint) + ? { owner: TOKEN_PROGRAM_ID, data: mintData() } + : { owner: TOKEN_PROGRAM_ID, data: Buffer.alloc(0) }, + } + await assert.rejects(() => + resolveExistingTokenAccount(invalidConnection as never, mint, holder, tokenAccount), + ) + }) + + it('accepts omitted and valid writable indexes', () => { + assert.doesNotThrow(() => validateWritableIndexes('op', 'writableIndexes', undefined)) + assert.doesNotThrow(() => validateWritableIndexes('op', 'writableIndexes', [0, 3, 255])) + }) + + it('rejects empty writable indexes', () => { + assert.throws( + () => validateWritableIndexes('op', 'writableIndexes', []), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'op' && + err.context.param === 'writableIndexes', + ) + }) + + it('rejects writable indexes outside byte range', () => { + assert.throws( + () => validateWritableIndexes('op', 'writableIndexes', [256]), + (err: unknown) => + err instanceof CCTParamsInvalidError && + err.context.operation === 'op' && + err.context.param === 'writableIndexes[0]', + ) + }) +}) diff --git a/ccip-sdk/src/cct/solana/validate.ts b/ccip-sdk/src/cct/solana/validate.ts new file mode 100644 index 000000000..913031c64 --- /dev/null +++ b/ccip-sdk/src/cct/solana/validate.ts @@ -0,0 +1,439 @@ +import { Buffer } from 'buffer' + +import { type Account, TokenAccountNotFoundError, getAccount } from '@solana/spl-token' +import { type Connection, PublicKey } from '@solana/web3.js' + +import { + CCIPAddressInvalidError, + CCIPTokenAccountNotFoundError, + CCIPTokenPoolStateNotFoundError, +} from '../../errors/index.ts' +import { ChainFamily } from '../../networks.ts' +import type { SolanaChain } from '../../solana/index.ts' +import { resolveATA } from '../../solana/utils.ts' +import { CCTParamsInvalidError, CCTTxFailedError } from '../errors.ts' +import { + type PoolProgramRef, + type TokenPoolType, + TOKEN_POOL_PROGRAMS, + decodeTokenPoolState, + deriveTokenPoolConfigPda, + resolveTokenPoolProgram, +} from './programs/token-pool.ts' + +/** Largest value representable by an unsigned 64-bit integer. */ +export const U64_MAX = 0xffff_ffff_ffff_ffffn + +/** + * Parses `value` as a Solana public key. + * @throws CCTParamsInvalidError if `value` is not a valid Solana public key string. + */ +export function parsePublicKey(operation: string, param: string, value: unknown): PublicKey { + if (typeof value !== 'string') { + throw new CCTParamsInvalidError( + operation, + param, + `must be a valid Solana public key, got "${String(value)}"`, + ) + } + + try { + return new PublicKey(value) + } catch { + throw new CCTParamsInvalidError( + operation, + param, + `must be a valid Solana public key, got "${String(value)}"`, + { + cause: new CCIPAddressInvalidError(value, ChainFamily.Solana), + }, + ) + } +} + +/** + * Asserts `value` is a valid Solana public key string. + * @throws CCTParamsInvalidError if `value` is not a valid Solana public key string. + */ +export function validatePublicKey( + operation: string, + param: string, + value: unknown, +): asserts value is string { + parsePublicKey(operation, param, value) +} + +/** + * Asserts `value` is a valid Solana public key string, or is absent. + * Only `undefined` counts as absent; `null` and `''` are treated as provided and rejected. + * @throws {@link CCTParamsInvalidError} if a non-`undefined` `value` is not a valid public key string. + */ +export function validateOptionalPublicKey( + operation: string, + param: string, + value: unknown, +): asserts value is string | undefined { + if (value !== undefined) validatePublicKey(operation, param, value) +} + +/** + * Asserts `values` is an array of valid Solana public key strings. + * @throws CCTParamsInvalidError if `values` is not an array or any item is invalid. + */ +export function validatePublicKeys(operation: string, param: string, values: unknown): void { + if (!Array.isArray(values)) throw new CCTParamsInvalidError(operation, param, 'must be an array') + for (const [i, value] of values.entries()) validatePublicKey(operation, `${param}[${i}]`, value) +} + +/** + * Asserts public keys do not contain duplicates. + * @throws CCTParamsInvalidError if a public key is duplicated. + */ +export function validateUniquePublicKeys( + operation: string, + param: string, + publicKeys: PublicKey[], +): void { + const seen = new Set() + for (const [i, publicKey] of publicKeys.entries()) { + const address = publicKey.toBase58() + if (seen.has(address)) { + throw new CCTParamsInvalidError( + operation, + `${param}[${i}]`, + 'must not contain duplicate addresses', + ) + } + seen.add(address) + } +} + +/** + * Asserts bigint chain selectors do not contain duplicates. + * @remarks Silently ignores non-bigint entries; relies on downstream `validateBigInt` for type safety. + * @throws CCTParamsInvalidError if a chain selector is duplicated. + */ +export function validateUniqueChainSelectors( + operation: string, + param: string, + selectors: unknown[], +): void { + const seen = new Set() + for (const [i, selector] of selectors.entries()) { + if (typeof selector === 'bigint' && seen.has(selector)) { + throw new CCTParamsInvalidError( + operation, + `${param}[${i}]`, + 'must not contain duplicate chain selectors', + ) + } + if (typeof selector === 'bigint') seen.add(selector) + } +} + +/** + * Asserts hex byte values do not contain duplicates. + * @throws CCTParamsInvalidError if a hex byte value is duplicated. + */ +export function validateUniqueHexBytes( + operation: string, + param: string, + values: Buffer[], + label = 'hex values', +): void { + const seen = new Set() + for (const [i, value] of values.entries()) { + const hex = value.toString('hex') + if (seen.has(hex)) { + throw new CCTParamsInvalidError( + operation, + `${param}[${i}]`, + `must not contain duplicate ${label}`, + ) + } + seen.add(hex) + } +} + +/** + * Asserts `value` is a non-empty string. + * @throws CCTParamsInvalidError if `value` is not a non-empty string. + */ +export function validateNonEmptyString(operation: string, param: string, value: unknown): void { + if (typeof value === 'string' && value.trim().length > 0) return + throw new CCTParamsInvalidError(operation, param, 'must be a non-empty string') +} + +/** + * Asserts an authority matches the executing wallet. + * @throws CCTParamsInvalidError if authority does not match wallet. + */ +export function validateAuthorityMatchesWallet( + operation: string, + authority: PublicKey, + wallet: PublicKey, + errorMessage = 'must match the executing wallet', +): void { + if (!authority.equals(wallet)) { + throw new CCTParamsInvalidError(operation, 'authority', errorMessage) + } +} + +/** + * Asserts `value` is a supported token pool type. + * @throws CCTParamsInvalidError if `value` is not `burn-mint` or `lock-release`. + */ +export function validatePoolType( + operation: string, + param: string, + value: unknown, +): asserts value is TokenPoolType { + if (typeof value !== 'string' || !Object.hasOwn(TOKEN_POOL_PROGRAMS, value)) { + throw new CCTParamsInvalidError(operation, param, 'must be burn-mint or lock-release') + } +} + +/** Resolves a canonical pool type or custom program address. */ +export function resolvePoolProgram(operation: string, params: PoolProgramRef): PublicKey { + // Value semantics: explicit undefined does not count as provided. + const hasPoolType = params.poolType !== undefined + const hasPoolProgramAddress = params.poolProgramAddress !== undefined + if (hasPoolType === hasPoolProgramAddress) { + throw new CCTParamsInvalidError( + operation, + 'poolType', + 'provide exactly one of poolType or poolProgramAddress', + ) + } + + if (hasPoolType) { + validatePoolType(operation, 'poolType', params.poolType) + return resolveTokenPoolProgram(params.poolType) + } + + return parsePublicKey(operation, 'poolProgramAddress', params.poolProgramAddress) +} + +/** Resolves a lock-release token pool program and rejects the canonical burn-mint program. */ +export function resolveLockReleasePoolProgram( + operation: string, + params: PoolProgramRef, +): PublicKey { + const poolProgram = resolvePoolProgram(operation, params) + if (poolProgram.equals(resolveTokenPoolProgram('burn-mint'))) { + throw new CCTParamsInvalidError( + operation, + params.poolProgramAddress === undefined ? 'poolType' : 'poolProgramAddress', + 'must be lock-release', + ) + } + return poolProgram +} + +/** + * Asserts `value` is an integer, optionally inside inclusive bounds. + * @throws CCTParamsInvalidError if `value` is not an integer or is outside bounds. + */ +export function validateInteger( + operation: string, + param: string, + value: unknown, + min?: number, + max?: number, +): void { + const validInteger = Number.isInteger(value) + const validMin = min === undefined || (validInteger && Number(value) >= min) + const validMax = max === undefined || (validInteger && Number(value) <= max) + + if (!validInteger || !validMin || !validMax) { + const range = + min !== undefined && max !== undefined + ? ` between ${min} and ${max}` + : min !== undefined + ? ` >= ${min}` + : max !== undefined + ? ` <= ${max}` + : '' + throw new CCTParamsInvalidError(operation, param, `must be an integer${range}`) + } +} + +/** + * Asserts `value` is a bigint, optionally inside inclusive bounds. + * @throws CCTParamsInvalidError if `value` is not a bigint or is outside bounds. + */ +export function validateBigInt( + operation: string, + param: string, + value: unknown, + min?: bigint, + max?: bigint, +): asserts value is bigint { + const validBigInt = typeof value === 'bigint' + const validMin = min === undefined || (validBigInt && value >= min) + const validMax = max === undefined || (validBigInt && value <= max) + + if (!validBigInt || !validMin || !validMax) { + const range = + min !== undefined && max !== undefined + ? ` between ${min} and ${max}` + : min !== undefined + ? ` >= ${min}` + : max !== undefined + ? ` <= ${max}` + : '' + throw new CCTParamsInvalidError(operation, param, `must be a bigint${range}`) + } +} + +/** + * Asserts ALT writable indexes are a non-empty list of byte values when provided. + * @throws CCTParamsInvalidError if indexes are empty or outside byte range. + */ +export function validateWritableIndexes( + operation: string, + param: string, + writableIndexes: unknown, +): void { + if (writableIndexes === undefined) return + if (!Array.isArray(writableIndexes) || writableIndexes.length === 0) { + throw new CCTParamsInvalidError(operation, param, 'must be a non-empty array') + } + + for (const [i, index] of writableIndexes.entries()) { + validateInteger(operation, `${param}[${i}]`, index, 0, 255) + } +} + +/** + * Parses an optionally `0x`-prefixed hex string into bytes, with an optional maximum size. + * @throws CCTParamsInvalidError if `value` is not valid hex or exceeds the requested size. + */ +export function parseHexBytes( + operation: string, + param: string, + value: unknown, + maxBytes?: number, +): Buffer { + const hex = typeof value === 'string' ? value.replace(/^0x/, '') : '' + if ( + typeof value !== 'string' || + !/^(?:[\da-fA-F]{2})*$/.test(hex) || + (maxBytes !== undefined && hex.length / 2 > maxBytes) + ) { + const size = maxBytes === undefined ? '' : ` of at most ${maxBytes} bytes` + throw new CCTParamsInvalidError(operation, param, `must be a hex string${size}`) + } + return Buffer.from(hex, 'hex') +} + +/** + * Parses a non-empty optionally `0x`-prefixed hex string into bytes. + * @throws CCTParamsInvalidError if `value` is not valid non-empty hex or exceeds the requested size. + */ +export function parseNonEmptyHexBytes( + operation: string, + param: string, + value: unknown, + maxBytes?: number, +): Buffer { + const bytes = parseHexBytes(operation, param, value, maxBytes) + if (!bytes.length) throw new CCTParamsInvalidError(operation, param, 'must not be empty') + return bytes +} + +/** + * Validates that a token account delegates at least an amount to the expected delegate. + * @throws {@link CCTTxFailedError} If the delegate is missing, differs, or has insufficient allowance. + */ +export function validateDelegation( + operation: string, + tokenAccount: PublicKey, + account: Account, + delegate: PublicKey, + amount: bigint, +): void { + if (account.delegate?.equals(delegate) && account.delegatedAmount >= amount) return + + const delegation = !account.delegate + ? 'has no delegate' + : !account.delegate.equals(delegate) + ? `delegates to ${account.delegate.toBase58()}` + : `delegates only ${account.delegatedAmount}` + throw new CCTTxFailedError( + operation, + `token account ${tokenAccount.toBase58()} ${delegation}; delegate at least ${amount} to ${delegate.toBase58()} with approveToken first`, + { + context: { + tokenAccount: tokenAccount.toBase58(), + delegate: account.delegate?.toBase58(), + expectedDelegate: delegate.toBase58(), + delegatedAmount: account.delegatedAmount.toString(), + }, + }, + ) +} + +/** + * Verifies that a rebalancer may move liquidity for a lock-release pool. + * @throws {@link CCIPTokenPoolStateNotFoundError} If the token pool state is missing. + * @throws {@link CCTTxFailedError} If the authority is not the rebalancer or liquidity is disabled. + */ +export async function validatePoolLiquidityConfig( + operation: string, + chain: SolanaChain, + poolProgram: PublicKey, + mint: PublicKey, + authority: PublicKey, +): Promise { + const state = deriveTokenPoolConfigPda(poolProgram, mint) + const account = await chain.connection.getAccountInfo(state) + if (!account) throw new CCIPTokenPoolStateNotFoundError(state.toBase58()) + + const { config } = decodeTokenPoolState(account.data, { + tokenPool: state.toBase58(), + mint: mint.toBase58(), + poolProgram: poolProgram.toBase58(), + accountOwner: account.owner.toBase58(), + }) + if (!config.rebalancer.equals(authority)) + throw new CCTTxFailedError( + operation, + `pool rebalancer is ${config.rebalancer.toBase58()}, not ${authority.toBase58()}; set it with setRebalancer first`, + ) + if (!config.canAcceptLiquidity) + throw new CCTTxFailedError( + operation, + 'pool does not accept liquidity; enable it with setCanAcceptLiquidity(true) first', + ) +} + +/** + * Resolves an existing token account, defaulting to the holder's associated token account. + * @throws {@link CCIPTokenAccountNotFoundError} If the token account does not exist. + */ +export async function resolveExistingTokenAccount( + connection: Connection, + tokenAddress: PublicKey, + holder: PublicKey, + tokenAccount?: PublicKey, +): Promise<{ + tokenAccount: PublicKey + tokenProgram: PublicKey + account: Account +}> { + const { ata, tokenProgram } = await resolveATA(connection, tokenAddress, holder) + const account = tokenAccount ?? ata + let tokenAccountInfo: Account + + try { + tokenAccountInfo = await getAccount(connection, account, undefined, tokenProgram) + } catch (error) { + if (error instanceof TokenAccountNotFoundError) { + throw new CCIPTokenAccountNotFoundError(tokenAddress.toBase58(), holder.toBase58()) + } + throw error + } + + return { tokenAccount: account, tokenProgram, account: tokenAccountInfo } +} diff --git a/ccip-sdk/src/cct/token-manager.ts b/ccip-sdk/src/cct/token-manager.ts new file mode 100644 index 000000000..9efe7b425 --- /dev/null +++ b/ccip-sdk/src/cct/token-manager.ts @@ -0,0 +1,18 @@ +/** + * Cross-family CCT manager base, the CCT analogue of core's {@link Chain}. + * Family-specific subclasses hold the chain and expose admin operations. + * + * @packageDocumentation + */ + +import type { Chain } from '../chain.ts' +import type { ChainFamily } from '../networks.ts' + +/** + * Abstract entry point for CCT admin writes on a chain family. Subclasses hold + * the concrete {@link Chain} and delegate to {@link Operation} instances. + */ +export abstract class TokenManager { + /** Chain this manager builds and submits through. */ + abstract readonly chain: Chain +} diff --git a/ccip-sdk/src/errors/codes.ts b/ccip-sdk/src/errors/codes.ts index 70e2f0410..c32443819 100644 --- a/ccip-sdk/src/errors/codes.ts +++ b/ccip-sdk/src/errors/codes.ts @@ -187,6 +187,14 @@ export const CCIPErrorCode = { // Canton CANTON_API_ERROR: 'CANTON_API_ERROR', CANTON_AUTH_ERROR: 'CANTON_AUTH_ERROR', + + // CCT (Cross-Chain Token) + CCT_PARAMS_INVALID: 'CCT_PARAMS_INVALID', + CCT_TX_FAILED: 'CCT_TX_FAILED', + CCT_TX_NOT_CONFIRMED: 'CCT_TX_NOT_CONFIRMED', + CCT_CONTRACT_VERSION_UNSUPPORTED: 'CCT_CONTRACT_VERSION_UNSUPPORTED', + CCT_OPERATION_UNSUPPORTED: 'CCT_OPERATION_UNSUPPORTED', + CCT_DATA_DECODE_FAILED: 'CCT_DATA_DECODE_FAILED', } as const /** Union type of all error codes. */ diff --git a/ccip-sdk/src/errors/errors.test.ts b/ccip-sdk/src/errors/errors.test.ts index 98db9aef7..927e89962 100644 --- a/ccip-sdk/src/errors/errors.test.ts +++ b/ccip-sdk/src/errors/errors.test.ts @@ -277,6 +277,13 @@ describe('recovery hints', () => { assert.ok(DEFAULT_RECOVERY_HINTS.BLOCK_NOT_FOUND?.includes('Wait')) assert.ok(DEFAULT_RECOVERY_HINTS.HTTP_ERROR?.includes('rate limiting')) }) + + it('should explain how to find a missing token pool state', () => { + assert.equal( + DEFAULT_RECOVERY_HINTS.TOKEN_POOL_STATE_NOT_FOUND, + 'Verify poolType matches the deployed pool, pass poolProgramAddress for a custom pool, and confirm the pool is initialized for this mint on this cluster.', + ) + }) }) describe('getDefaultRecovery', () => { diff --git a/ccip-sdk/src/errors/recovery.ts b/ccip-sdk/src/errors/recovery.ts index 7350e5679..1037a84a5 100644 --- a/ccip-sdk/src/errors/recovery.ts +++ b/ccip-sdk/src/errors/recovery.ts @@ -113,7 +113,8 @@ export const DEFAULT_RECOVERY_HINTS: Partial> = { TOKEN_MINT_INVALID: 'The address is not a valid SPL token mint. Ensure the address is owned by TOKEN_PROGRAM_ID or TOKEN_2022_PROGRAM_ID.', TOKEN_AMOUNT_INVALID: 'Token amount must have a valid address and positive amount.', - TOKEN_POOL_STATE_NOT_FOUND: 'TokenPool state PDA not found.', + TOKEN_POOL_STATE_NOT_FOUND: + 'Verify poolType matches the deployed pool, pass poolProgramAddress for a custom pool, and confirm the pool is initialized for this mint on this cluster.', TOKEN_POOL_INFO_NOT_FOUND: 'Check that the token pool is deployed and configured for this lane. Verify supported tokens: https://docs.chain.link/ccip/directory', TOKEN_ACCOUNT_NOT_FOUND: @@ -220,6 +221,20 @@ export const DEFAULT_RECOVERY_HINTS: Partial> = { 'Canton Ledger API returned an error. Verify the party ID is correct, the contract is active, and the Canton node is reachable.', CANTON_AUTH_ERROR: 'Canton authentication failed. Verify the JWT is valid and not expired, or check the OIDC auth_url, client_id, and client_secret (client credentials) or redirect URI (authorization code).', + + // Cross-Chain Token + CCT_PARAMS_INVALID: + 'Verify the operation parameters. See error.context for the field name and reason.', + CCT_TX_FAILED: + 'The CCT transaction failed. Ensure the caller holds the required role for this operation.', + CCT_TX_NOT_CONFIRMED: + 'The transaction was submitted but not confirmed in time. Check the tx hash in error.context before resubmitting; it may still be mined.', + CCT_CONTRACT_VERSION_UNSUPPORTED: + 'This contract version is not supported by the CCT SDK. Check the contract address and its typeAndVersion.', + CCT_OPERATION_UNSUPPORTED: + 'This operation is not available at the contract version in error.context. Verify the contract version supports it.', + CCT_DATA_DECODE_FAILED: + 'Ensure the account belongs to a compatible CCT program and uses the expected data layout.', } /** Returns default recovery hint for error code, or undefined if none. */ diff --git a/ccip-sdk/src/evm/index.ts b/ccip-sdk/src/evm/index.ts index 50820dcb7..6b6139be9 100644 --- a/ccip-sdk/src/evm/index.ts +++ b/ccip-sdk/src/evm/index.ts @@ -238,7 +238,7 @@ function encodeAddressToEvm(address: BytesLike): string { } /** typeguard for ethers Signer interface (used for `wallet`s) */ -function isSigner(wallet: unknown): wallet is Signer { +export function isSigner(wallet: unknown): wallet is Signer { return ( typeof wallet === 'object' && wallet !== null && @@ -252,7 +252,7 @@ function isSigner(wallet: unknown): wallet is Signer { * Try sendTransaction() first (works with browser wallets), * fallback to signTransaction() + broadcastTransaction() if unsupported. */ -async function submitTransaction( +export async function submitTransaction( wallet: Signer, tx: TransactionRequest, provider: JsonRpcApiProvider, @@ -512,6 +512,17 @@ export class EVMChain extends Chain { return this.nonces[address]!++ } + /** + * Undo the last {@link nextNonce} increment for a wallet address. + * {@link nextNonce} hands out a nonce optimistically; if the send then fails + * before broadcast, call this so the counter is reused rather than leaving a + * permanent gap that stalls every later transaction. No-op if uncached. + * @param address - Wallet address whose cached nonce to roll back + */ + rollbackNonce(address: string): void { + if (this.nonces[address] != null) this.nonces[address]-- + } + /** * Creates a JSON-RPC provider from a URL. * @param url - WebSocket (wss://) or HTTP (https://) endpoint URL. diff --git a/ccip-sdk/src/solana/__tests__/index.test.ts b/ccip-sdk/src/solana/__tests__/index.test.ts index cbf963d5d..321666b7a 100644 --- a/ccip-sdk/src/solana/__tests__/index.test.ts +++ b/ccip-sdk/src/solana/__tests__/index.test.ts @@ -1,9 +1,14 @@ import assert from 'node:assert/strict' import { beforeEach, describe, it, mock } from 'node:test' +import { BorshAccountsCoder } from '@coral-xyz/anchor' import { type Connection, PublicKey } from '@solana/web3.js' -import { CCIPCommitHistoryPrunedError, CCIPCommitNotFoundError } from '../../errors/index.ts' +import { + CCIPCommitHistoryPrunedError, + CCIPCommitNotFoundError, + CCIPDataFormatUnsupportedError, +} from '../../errors/index.ts' import { type NetworkInfo, ChainFamily, NetworkType } from '../../networks.ts' import { CCIPVersion } from '../../types.ts' import { type SolanaTransaction, SolanaChain } from '../index.ts' @@ -11,6 +16,7 @@ import { hexDiscriminator } from '../utils.ts' // Create mock functions const mockGetAccountInfo = mock.fn(() => null as any) +const mockGetAddressLookupTable = mock.fn(() => null as any) const mockGetParsedAccountInfo = mock.fn(() => null as any) const mockGetGenesisHash = mock.fn(() => null as any) const mockGetSignaturesForAddress = mock.fn(() => null as any) @@ -22,6 +28,7 @@ const mockConnection = { getGenesisHash: mockGetGenesisHash, getParsedAccountInfo: mockGetParsedAccountInfo, getAccountInfo: mockGetAccountInfo, + getAddressLookupTable: mockGetAddressLookupTable, getSignaturesForAddress: mockGetSignaturesForAddress, getProgramAccounts: mockGetProgramAccounts, } as unknown as Connection @@ -616,6 +623,75 @@ describe('SolanaChain.encodeExtraArgs', () => { }) }) +describe('SolanaChain getRegistryTokenConfig', () => { + const key = (byte: number): PublicKey => { + return new PublicKey(Uint8Array.from({ length: 32 }, () => byte)) + } + + const router = key(1) + const mint = key(2) + const administrator = key(3) + const pendingAdministrator = key(4) + const lookupTable = key(5) + const tokenPool = key(6) + + function tokenAdminRegistryData( + administrator: PublicKey, + pendingAdministrator: PublicKey, + lookupTable: PublicKey, + mint: PublicKey, + ): Buffer { + const data = Buffer.alloc(170) + BorshAccountsCoder.accountDiscriminator('TokenAdminRegistry').copy(data) + data[8] = 2 + administrator.toBuffer().copy(data, 9) + pendingAdministrator.toBuffer().copy(data, 41) + lookupTable.toBuffer().copy(data, 73) + mint.toBuffer().copy(data, 137) + return data + } + + function chainWithLookupTable(lookup: () => Promise): SolanaChain { + return new SolanaChain( + { + getAccountInfo: async () => ({ + data: tokenAdminRegistryData(administrator, pendingAdministrator, lookupTable, mint), + }), + getAddressLookupTable: lookup, + getSignaturesForAddress: async () => [], + } as unknown as Connection, + mockNetworkInfo, + ) + } + + it('returns the configured administrator, pending administrator, and token pool', async () => { + const chain = chainWithLookupTable(async () => ({ + value: { + state: { + addresses: [PublicKey.default, PublicKey.default, PublicKey.default, tokenPool], + }, + }, + })) + + assert.deepEqual(await chain.getRegistryTokenConfig(router.toBase58(), mint.toBase58()), { + administrator: administrator.toBase58(), + pendingAdministrator: pendingAdministrator.toBase58(), + tokenPool: tokenPool.toBase58(), + }) + }) + + it('omits the token pool when lookup-table resolution fails', async () => { + const chain = chainWithLookupTable(async () => { + throw new CCIPDataFormatUnsupportedError('RPC unavailable') + }) + + assert.deepEqual(await chain.getRegistryTokenConfig(router.toBase58(), mint.toBase58()), { + administrator: administrator.toBase58(), + pendingAdministrator: pendingAdministrator.toBase58(), + }) + }) +}) + describe('SolanaChain getExecutionReceipts', () => { let solanaChain: SolanaChain diff --git a/ccip-sdk/src/solana/idl/1.6.0/LOCK_RELEASE_TOKEN_POOL.ts b/ccip-sdk/src/solana/idl/1.6.0/LOCK_RELEASE_TOKEN_POOL.ts new file mode 100644 index 000000000..aee4794fb --- /dev/null +++ b/ccip-sdk/src/solana/idl/1.6.0/LOCK_RELEASE_TOKEN_POOL.ts @@ -0,0 +1,1972 @@ +// generate: +// fetch('https://raw.githubusercontent.com/smartcontractkit/chainlink-ccip/refs/heads/main/chains/solana/contracts/target/types/lockrelease_token_pool.ts') +// .then((res) => res.text()) +// .then((text) => text.trim()) +export type LockreleaseTokenPool = { + version: '1.6.4' + name: 'lockrelease_token_pool' + instructions: [ + { + name: 'initGlobalConfig' + accounts: [ + { + name: 'config' + isMut: true + isSigner: false + }, + { + name: 'authority' + isMut: true + isSigner: true + }, + { + name: 'systemProgram' + isMut: false + isSigner: false + }, + { + name: 'program' + isMut: false + isSigner: false + }, + { + name: 'programData' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'routerAddress' + type: 'publicKey' + }, + { + name: 'rmnAddress' + type: 'publicKey' + }, + ] + }, + { + name: 'updateSelfServedAllowed' + accounts: [ + { + name: 'config' + isMut: true + isSigner: false + }, + { + name: 'authority' + isMut: false + isSigner: true + }, + { + name: 'program' + isMut: false + isSigner: false + }, + { + name: 'programData' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'selfServedAllowed' + type: 'bool' + }, + ] + }, + { + name: 'updateDefaultRouter' + accounts: [ + { + name: 'config' + isMut: true + isSigner: false + }, + { + name: 'authority' + isMut: false + isSigner: true + }, + { + name: 'program' + isMut: false + isSigner: false + }, + { + name: 'programData' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'routerAddress' + type: 'publicKey' + }, + ] + }, + { + name: 'updateDefaultRmn' + accounts: [ + { + name: 'config' + isMut: true + isSigner: false + }, + { + name: 'authority' + isMut: false + isSigner: true + }, + { + name: 'program' + isMut: false + isSigner: false + }, + { + name: 'programData' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'rmnAddress' + type: 'publicKey' + }, + ] + }, + { + name: 'initialize' + accounts: [ + { + name: 'state' + isMut: true + isSigner: false + }, + { + name: 'mint' + isMut: false + isSigner: false + }, + { + name: 'authority' + isMut: true + isSigner: true + }, + { + name: 'systemProgram' + isMut: false + isSigner: false + }, + { + name: 'program' + isMut: false + isSigner: false + }, + { + name: 'programData' + isMut: false + isSigner: false + }, + { + name: 'config' + isMut: false + isSigner: false + }, + ] + args: [] + }, + { + name: 'typeVersion' + docs: [ + 'Returns the program type (name) and version.', + 'Used by offchain code to easily determine which program & version is being interacted with.', + '', + '# Arguments', + '* `ctx` - The context', + ] + accounts: [ + { + name: 'clock' + isMut: false + isSigner: false + }, + ] + args: [] + returns: 'string' + }, + { + name: 'transferOwnership' + accounts: [ + { + name: 'state' + isMut: true + isSigner: false + }, + { + name: 'mint' + isMut: false + isSigner: false + }, + { + name: 'authority' + isMut: false + isSigner: true + }, + ] + args: [ + { + name: 'proposedOwner' + type: 'publicKey' + }, + ] + }, + { + name: 'acceptOwnership' + accounts: [ + { + name: 'state' + isMut: true + isSigner: false + }, + { + name: 'mint' + isMut: false + isSigner: false + }, + { + name: 'authority' + isMut: false + isSigner: true + }, + ] + args: [] + }, + { + name: 'setRouter' + accounts: [ + { + name: 'state' + isMut: true + isSigner: false + }, + { + name: 'mint' + isMut: false + isSigner: false + }, + { + name: 'authority' + isMut: true + isSigner: true + }, + { + name: 'program' + isMut: false + isSigner: false + }, + { + name: 'programData' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'newRouter' + type: 'publicKey' + }, + ] + }, + { + name: 'setRmn' + accounts: [ + { + name: 'state' + isMut: true + isSigner: false + }, + { + name: 'mint' + isMut: false + isSigner: false + }, + { + name: 'authority' + isMut: true + isSigner: true + }, + { + name: 'program' + isMut: false + isSigner: false + }, + { + name: 'programData' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'rmnAddress' + type: 'publicKey' + }, + ] + }, + { + name: 'initializeStateVersion' + accounts: [ + { + name: 'state' + isMut: true + isSigner: false + }, + ] + args: [ + { + name: 'mint' + type: 'publicKey' + }, + ] + }, + { + name: 'initChainRemoteConfig' + accounts: [ + { + name: 'state' + isMut: false + isSigner: false + }, + { + name: 'chainConfig' + isMut: true + isSigner: false + }, + { + name: 'authority' + isMut: true + isSigner: true + }, + { + name: 'systemProgram' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'remoteChainSelector' + type: 'u64' + }, + { + name: 'mint' + type: 'publicKey' + }, + { + name: 'cfg' + type: { + defined: 'RemoteConfig' + } + }, + ] + }, + { + name: 'editChainRemoteConfig' + accounts: [ + { + name: 'state' + isMut: false + isSigner: false + }, + { + name: 'chainConfig' + isMut: true + isSigner: false + }, + { + name: 'authority' + isMut: true + isSigner: true + }, + { + name: 'systemProgram' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'remoteChainSelector' + type: 'u64' + }, + { + name: 'mint' + type: 'publicKey' + }, + { + name: 'cfg' + type: { + defined: 'RemoteConfig' + } + }, + ] + }, + { + name: 'appendRemotePoolAddresses' + accounts: [ + { + name: 'state' + isMut: false + isSigner: false + }, + { + name: 'chainConfig' + isMut: true + isSigner: false + }, + { + name: 'authority' + isMut: true + isSigner: true + }, + { + name: 'systemProgram' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'remoteChainSelector' + type: 'u64' + }, + { + name: 'mint' + type: 'publicKey' + }, + { + name: 'addresses' + type: { + vec: { + defined: 'RemoteAddress' + } + } + }, + ] + }, + { + name: 'setChainRateLimit' + accounts: [ + { + name: 'state' + isMut: false + isSigner: false + }, + { + name: 'chainConfig' + isMut: true + isSigner: false + }, + { + name: 'authority' + isMut: true + isSigner: true + }, + ] + args: [ + { + name: 'remoteChainSelector' + type: 'u64' + }, + { + name: 'mint' + type: 'publicKey' + }, + { + name: 'inbound' + type: { + defined: 'RateLimitConfig' + } + }, + { + name: 'outbound' + type: { + defined: 'RateLimitConfig' + } + }, + ] + }, + { + name: 'setRateLimitAdmin' + accounts: [ + { + name: 'state' + isMut: true + isSigner: false + }, + { + name: 'authority' + isMut: true + isSigner: true + }, + ] + args: [ + { + name: 'mint' + type: 'publicKey' + }, + { + name: 'newRateLimitAdmin' + type: 'publicKey' + }, + ] + }, + { + name: 'deleteChainConfig' + accounts: [ + { + name: 'state' + isMut: false + isSigner: false + }, + { + name: 'chainConfig' + isMut: true + isSigner: false + }, + { + name: 'authority' + isMut: true + isSigner: true + }, + ] + args: [ + { + name: 'remoteChainSelector' + type: 'u64' + }, + { + name: 'mint' + type: 'publicKey' + }, + ] + }, + { + name: 'configureAllowList' + accounts: [ + { + name: 'state' + isMut: true + isSigner: false + }, + { + name: 'mint' + isMut: false + isSigner: false + }, + { + name: 'authority' + isMut: true + isSigner: true + }, + { + name: 'systemProgram' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'add' + type: { + vec: 'publicKey' + } + }, + { + name: 'enabled' + type: 'bool' + }, + ] + }, + { + name: 'removeFromAllowList' + accounts: [ + { + name: 'state' + isMut: true + isSigner: false + }, + { + name: 'mint' + isMut: false + isSigner: false + }, + { + name: 'authority' + isMut: true + isSigner: true + }, + { + name: 'systemProgram' + isMut: false + isSigner: false + }, + ] + args: [ + { + name: 'remove' + type: { + vec: 'publicKey' + } + }, + ] + }, + { + name: 'releaseOrMintTokens' + accounts: [ + { + name: 'authority' + isMut: false + isSigner: true + }, + { + name: 'offrampProgram' + isMut: false + isSigner: false + docs: [ + 'CHECK offramp program: exists only to derive the allowed offramp PDA', + 'and the authority PDA.', + ] + }, + { + name: 'allowedOfframp' + isMut: false + isSigner: false + docs: [ + 'CHECK PDA of the router program verifying the signer is an allowed offramp.', + "If PDA does not exist, the router doesn't allow this offramp", + ] + }, + { + name: 'state' + isMut: false + isSigner: false + }, + { + name: 'tokenProgram' + isMut: false + isSigner: false + }, + { + name: 'mint' + isMut: true + isSigner: false + }, + { + name: 'poolSigner' + isMut: false + isSigner: false + }, + { + name: 'poolTokenAccount' + isMut: true + isSigner: false + }, + { + name: 'chainConfig' + isMut: true + isSigner: false + }, + { + name: 'rmnRemote' + isMut: false + isSigner: false + }, + { + name: 'rmnRemoteCurses' + isMut: false + isSigner: false + }, + { + name: 'rmnRemoteConfig' + isMut: false + isSigner: false + }, + { + name: 'receiverTokenAccount' + isMut: true + isSigner: false + }, + ] + args: [ + { + name: 'releaseOrMint' + type: { + defined: 'ReleaseOrMintInV1' + } + }, + ] + returns: { + defined: 'ReleaseOrMintOutV1' + } + }, + { + name: 'lockOrBurnTokens' + accounts: [ + { + name: 'authority' + isMut: false + isSigner: true + }, + { + name: 'state' + isMut: false + isSigner: false + }, + { + name: 'tokenProgram' + isMut: false + isSigner: false + }, + { + name: 'mint' + isMut: true + isSigner: false + }, + { + name: 'poolSigner' + isMut: false + isSigner: false + }, + { + name: 'poolTokenAccount' + isMut: true + isSigner: false + }, + { + name: 'rmnRemote' + isMut: false + isSigner: false + }, + { + name: 'rmnRemoteCurses' + isMut: false + isSigner: false + }, + { + name: 'rmnRemoteConfig' + isMut: false + isSigner: false + }, + { + name: 'chainConfig' + isMut: true + isSigner: false + }, + ] + args: [ + { + name: 'lockOrBurn' + type: { + defined: 'LockOrBurnInV1' + } + }, + ] + returns: { + defined: 'LockOrBurnOutV1' + } + }, + { + name: 'setRebalancer' + accounts: [ + { + name: 'state' + isMut: true + isSigner: false + }, + { + name: 'mint' + isMut: false + isSigner: false + }, + { + name: 'authority' + isMut: false + isSigner: true + }, + ] + args: [ + { + name: 'rebalancer' + type: 'publicKey' + }, + ] + }, + { + name: 'setCanAcceptLiquidity' + accounts: [ + { + name: 'state' + isMut: true + isSigner: false + }, + { + name: 'mint' + isMut: false + isSigner: false + }, + { + name: 'authority' + isMut: false + isSigner: true + }, + ] + args: [ + { + name: 'allow' + type: 'bool' + }, + ] + }, + { + name: 'provideLiquidity' + accounts: [ + { + name: 'state' + isMut: false + isSigner: false + }, + { + name: 'tokenProgram' + isMut: false + isSigner: false + }, + { + name: 'mint' + isMut: true + isSigner: false + }, + { + name: 'poolSigner' + isMut: false + isSigner: false + }, + { + name: 'poolTokenAccount' + isMut: true + isSigner: false + }, + { + name: 'remoteTokenAccount' + isMut: true + isSigner: false + }, + { + name: 'authority' + isMut: false + isSigner: true + }, + ] + args: [ + { + name: 'amount' + type: 'u64' + }, + ] + }, + { + name: 'withdrawLiquidity' + accounts: [ + { + name: 'state' + isMut: false + isSigner: false + }, + { + name: 'tokenProgram' + isMut: false + isSigner: false + }, + { + name: 'mint' + isMut: true + isSigner: false + }, + { + name: 'poolSigner' + isMut: false + isSigner: false + }, + { + name: 'poolTokenAccount' + isMut: true + isSigner: false + }, + { + name: 'remoteTokenAccount' + isMut: true + isSigner: false + }, + { + name: 'authority' + isMut: false + isSigner: true + }, + ] + args: [ + { + name: 'amount' + type: 'u64' + }, + ] + }, + ] + accounts: [ + { + name: 'poolConfig' + type: { + kind: 'struct' + fields: [ + { + name: 'version' + type: 'u8' + }, + { + name: 'selfServedAllowed' + type: 'bool' + }, + { + name: 'router' + type: 'publicKey' + }, + { + name: 'rmnRemote' + type: 'publicKey' + }, + ] + } + }, + { + name: 'state' + type: { + kind: 'struct' + fields: [ + { + name: 'version' + type: 'u8' + }, + { + name: 'config' + type: { + defined: 'BaseConfig' + } + }, + ] + } + }, + { + name: 'chainConfig' + type: { + kind: 'struct' + fields: [ + { + name: 'base' + type: { + defined: 'BaseChain' + } + }, + ] + } + }, + ] +} + +export const IDL: LockreleaseTokenPool = { + version: '1.6.4', + name: 'lockrelease_token_pool', + instructions: [ + { + name: 'initGlobalConfig', + accounts: [ + { + name: 'config', + isMut: true, + isSigner: false, + }, + { + name: 'authority', + isMut: true, + isSigner: true, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + { + name: 'program', + isMut: false, + isSigner: false, + }, + { + name: 'programData', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'routerAddress', + type: 'publicKey', + }, + { + name: 'rmnAddress', + type: 'publicKey', + }, + ], + }, + { + name: 'updateSelfServedAllowed', + accounts: [ + { + name: 'config', + isMut: true, + isSigner: false, + }, + { + name: 'authority', + isMut: false, + isSigner: true, + }, + { + name: 'program', + isMut: false, + isSigner: false, + }, + { + name: 'programData', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'selfServedAllowed', + type: 'bool', + }, + ], + }, + { + name: 'updateDefaultRouter', + accounts: [ + { + name: 'config', + isMut: true, + isSigner: false, + }, + { + name: 'authority', + isMut: false, + isSigner: true, + }, + { + name: 'program', + isMut: false, + isSigner: false, + }, + { + name: 'programData', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'routerAddress', + type: 'publicKey', + }, + ], + }, + { + name: 'updateDefaultRmn', + accounts: [ + { + name: 'config', + isMut: true, + isSigner: false, + }, + { + name: 'authority', + isMut: false, + isSigner: true, + }, + { + name: 'program', + isMut: false, + isSigner: false, + }, + { + name: 'programData', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'rmnAddress', + type: 'publicKey', + }, + ], + }, + { + name: 'initialize', + accounts: [ + { + name: 'state', + isMut: true, + isSigner: false, + }, + { + name: 'mint', + isMut: false, + isSigner: false, + }, + { + name: 'authority', + isMut: true, + isSigner: true, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + { + name: 'program', + isMut: false, + isSigner: false, + }, + { + name: 'programData', + isMut: false, + isSigner: false, + }, + { + name: 'config', + isMut: false, + isSigner: false, + }, + ], + args: [], + }, + { + name: 'typeVersion', + docs: [ + 'Returns the program type (name) and version.', + 'Used by offchain code to easily determine which program & version is being interacted with.', + '', + '# Arguments', + '* `ctx` - The context', + ], + accounts: [ + { + name: 'clock', + isMut: false, + isSigner: false, + }, + ], + args: [], + returns: 'string', + }, + { + name: 'transferOwnership', + accounts: [ + { + name: 'state', + isMut: true, + isSigner: false, + }, + { + name: 'mint', + isMut: false, + isSigner: false, + }, + { + name: 'authority', + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: 'proposedOwner', + type: 'publicKey', + }, + ], + }, + { + name: 'acceptOwnership', + accounts: [ + { + name: 'state', + isMut: true, + isSigner: false, + }, + { + name: 'mint', + isMut: false, + isSigner: false, + }, + { + name: 'authority', + isMut: false, + isSigner: true, + }, + ], + args: [], + }, + { + name: 'setRouter', + accounts: [ + { + name: 'state', + isMut: true, + isSigner: false, + }, + { + name: 'mint', + isMut: false, + isSigner: false, + }, + { + name: 'authority', + isMut: true, + isSigner: true, + }, + { + name: 'program', + isMut: false, + isSigner: false, + }, + { + name: 'programData', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'newRouter', + type: 'publicKey', + }, + ], + }, + { + name: 'setRmn', + accounts: [ + { + name: 'state', + isMut: true, + isSigner: false, + }, + { + name: 'mint', + isMut: false, + isSigner: false, + }, + { + name: 'authority', + isMut: true, + isSigner: true, + }, + { + name: 'program', + isMut: false, + isSigner: false, + }, + { + name: 'programData', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'rmnAddress', + type: 'publicKey', + }, + ], + }, + { + name: 'initializeStateVersion', + accounts: [ + { + name: 'state', + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: 'mint', + type: 'publicKey', + }, + ], + }, + { + name: 'initChainRemoteConfig', + accounts: [ + { + name: 'state', + isMut: false, + isSigner: false, + }, + { + name: 'chainConfig', + isMut: true, + isSigner: false, + }, + { + name: 'authority', + isMut: true, + isSigner: true, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'remoteChainSelector', + type: 'u64', + }, + { + name: 'mint', + type: 'publicKey', + }, + { + name: 'cfg', + type: { + defined: 'RemoteConfig', + }, + }, + ], + }, + { + name: 'editChainRemoteConfig', + accounts: [ + { + name: 'state', + isMut: false, + isSigner: false, + }, + { + name: 'chainConfig', + isMut: true, + isSigner: false, + }, + { + name: 'authority', + isMut: true, + isSigner: true, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'remoteChainSelector', + type: 'u64', + }, + { + name: 'mint', + type: 'publicKey', + }, + { + name: 'cfg', + type: { + defined: 'RemoteConfig', + }, + }, + ], + }, + { + name: 'appendRemotePoolAddresses', + accounts: [ + { + name: 'state', + isMut: false, + isSigner: false, + }, + { + name: 'chainConfig', + isMut: true, + isSigner: false, + }, + { + name: 'authority', + isMut: true, + isSigner: true, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'remoteChainSelector', + type: 'u64', + }, + { + name: 'mint', + type: 'publicKey', + }, + { + name: 'addresses', + type: { + vec: { + defined: 'RemoteAddress', + }, + }, + }, + ], + }, + { + name: 'setChainRateLimit', + accounts: [ + { + name: 'state', + isMut: false, + isSigner: false, + }, + { + name: 'chainConfig', + isMut: true, + isSigner: false, + }, + { + name: 'authority', + isMut: true, + isSigner: true, + }, + ], + args: [ + { + name: 'remoteChainSelector', + type: 'u64', + }, + { + name: 'mint', + type: 'publicKey', + }, + { + name: 'inbound', + type: { + defined: 'RateLimitConfig', + }, + }, + { + name: 'outbound', + type: { + defined: 'RateLimitConfig', + }, + }, + ], + }, + { + name: 'setRateLimitAdmin', + accounts: [ + { + name: 'state', + isMut: true, + isSigner: false, + }, + { + name: 'authority', + isMut: true, + isSigner: true, + }, + ], + args: [ + { + name: 'mint', + type: 'publicKey', + }, + { + name: 'newRateLimitAdmin', + type: 'publicKey', + }, + ], + }, + { + name: 'deleteChainConfig', + accounts: [ + { + name: 'state', + isMut: false, + isSigner: false, + }, + { + name: 'chainConfig', + isMut: true, + isSigner: false, + }, + { + name: 'authority', + isMut: true, + isSigner: true, + }, + ], + args: [ + { + name: 'remoteChainSelector', + type: 'u64', + }, + { + name: 'mint', + type: 'publicKey', + }, + ], + }, + { + name: 'configureAllowList', + accounts: [ + { + name: 'state', + isMut: true, + isSigner: false, + }, + { + name: 'mint', + isMut: false, + isSigner: false, + }, + { + name: 'authority', + isMut: true, + isSigner: true, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'add', + type: { + vec: 'publicKey', + }, + }, + { + name: 'enabled', + type: 'bool', + }, + ], + }, + { + name: 'removeFromAllowList', + accounts: [ + { + name: 'state', + isMut: true, + isSigner: false, + }, + { + name: 'mint', + isMut: false, + isSigner: false, + }, + { + name: 'authority', + isMut: true, + isSigner: true, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'remove', + type: { + vec: 'publicKey', + }, + }, + ], + }, + { + name: 'releaseOrMintTokens', + accounts: [ + { + name: 'authority', + isMut: false, + isSigner: true, + }, + { + name: 'offrampProgram', + isMut: false, + isSigner: false, + docs: [ + 'CHECK offramp program: exists only to derive the allowed offramp PDA', + 'and the authority PDA.', + ], + }, + { + name: 'allowedOfframp', + isMut: false, + isSigner: false, + docs: [ + 'CHECK PDA of the router program verifying the signer is an allowed offramp.', + "If PDA does not exist, the router doesn't allow this offramp", + ], + }, + { + name: 'state', + isMut: false, + isSigner: false, + }, + { + name: 'tokenProgram', + isMut: false, + isSigner: false, + }, + { + name: 'mint', + isMut: true, + isSigner: false, + }, + { + name: 'poolSigner', + isMut: false, + isSigner: false, + }, + { + name: 'poolTokenAccount', + isMut: true, + isSigner: false, + }, + { + name: 'chainConfig', + isMut: true, + isSigner: false, + }, + { + name: 'rmnRemote', + isMut: false, + isSigner: false, + }, + { + name: 'rmnRemoteCurses', + isMut: false, + isSigner: false, + }, + { + name: 'rmnRemoteConfig', + isMut: false, + isSigner: false, + }, + { + name: 'receiverTokenAccount', + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: 'releaseOrMint', + type: { + defined: 'ReleaseOrMintInV1', + }, + }, + ], + returns: { + defined: 'ReleaseOrMintOutV1', + }, + }, + { + name: 'lockOrBurnTokens', + accounts: [ + { + name: 'authority', + isMut: false, + isSigner: true, + }, + { + name: 'state', + isMut: false, + isSigner: false, + }, + { + name: 'tokenProgram', + isMut: false, + isSigner: false, + }, + { + name: 'mint', + isMut: true, + isSigner: false, + }, + { + name: 'poolSigner', + isMut: false, + isSigner: false, + }, + { + name: 'poolTokenAccount', + isMut: true, + isSigner: false, + }, + { + name: 'rmnRemote', + isMut: false, + isSigner: false, + }, + { + name: 'rmnRemoteCurses', + isMut: false, + isSigner: false, + }, + { + name: 'rmnRemoteConfig', + isMut: false, + isSigner: false, + }, + { + name: 'chainConfig', + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: 'lockOrBurn', + type: { + defined: 'LockOrBurnInV1', + }, + }, + ], + returns: { + defined: 'LockOrBurnOutV1', + }, + }, + { + name: 'setRebalancer', + accounts: [ + { + name: 'state', + isMut: true, + isSigner: false, + }, + { + name: 'mint', + isMut: false, + isSigner: false, + }, + { + name: 'authority', + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: 'rebalancer', + type: 'publicKey', + }, + ], + }, + { + name: 'setCanAcceptLiquidity', + accounts: [ + { + name: 'state', + isMut: true, + isSigner: false, + }, + { + name: 'mint', + isMut: false, + isSigner: false, + }, + { + name: 'authority', + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: 'allow', + type: 'bool', + }, + ], + }, + { + name: 'provideLiquidity', + accounts: [ + { + name: 'state', + isMut: false, + isSigner: false, + }, + { + name: 'tokenProgram', + isMut: false, + isSigner: false, + }, + { + name: 'mint', + isMut: true, + isSigner: false, + }, + { + name: 'poolSigner', + isMut: false, + isSigner: false, + }, + { + name: 'poolTokenAccount', + isMut: true, + isSigner: false, + }, + { + name: 'remoteTokenAccount', + isMut: true, + isSigner: false, + }, + { + name: 'authority', + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: 'amount', + type: 'u64', + }, + ], + }, + { + name: 'withdrawLiquidity', + accounts: [ + { + name: 'state', + isMut: false, + isSigner: false, + }, + { + name: 'tokenProgram', + isMut: false, + isSigner: false, + }, + { + name: 'mint', + isMut: true, + isSigner: false, + }, + { + name: 'poolSigner', + isMut: false, + isSigner: false, + }, + { + name: 'poolTokenAccount', + isMut: true, + isSigner: false, + }, + { + name: 'remoteTokenAccount', + isMut: true, + isSigner: false, + }, + { + name: 'authority', + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: 'amount', + type: 'u64', + }, + ], + }, + ], + accounts: [ + { + name: 'poolConfig', + type: { + kind: 'struct', + fields: [ + { + name: 'version', + type: 'u8', + }, + { + name: 'selfServedAllowed', + type: 'bool', + }, + { + name: 'router', + type: 'publicKey', + }, + { + name: 'rmnRemote', + type: 'publicKey', + }, + ], + }, + }, + { + name: 'state', + type: { + kind: 'struct', + fields: [ + { + name: 'version', + type: 'u8', + }, + { + name: 'config', + type: { + defined: 'BaseConfig', + }, + }, + ], + }, + }, + { + name: 'chainConfig', + type: { + kind: 'struct', + fields: [ + { + name: 'base', + type: { + defined: 'BaseChain', + }, + }, + ], + }, + }, + ], +} +// generate:end diff --git a/ccip-sdk/src/solana/idl/token-pool-coder.ts b/ccip-sdk/src/solana/idl/token-pool-coder.ts new file mode 100644 index 000000000..3237b517e --- /dev/null +++ b/ccip-sdk/src/solana/idl/token-pool-coder.ts @@ -0,0 +1,30 @@ +import { type Idl, type IdlTypes, BorshCoder } from '@coral-xyz/anchor' + +import { IDL as BASE_TOKEN_POOL } from './1.6.0/BASE_TOKEN_POOL.ts' +import { IDL as BURN_MINT_TOKEN_POOL } from './1.6.0/BURN_MINT_TOKEN_POOL.ts' +import { IDL as LOCK_RELEASE_TOKEN_POOL } from './1.6.0/LOCK_RELEASE_TOKEN_POOL.ts' + +/** Adds shared base token-pool types, events, and errors to a pool-specific IDL. */ +function composeTokenPoolIdl(poolIdl: T) { + return { + ...poolIdl, + types: BASE_TOKEN_POOL.types, + events: BASE_TOKEN_POOL.events, + errors: [...BASE_TOKEN_POOL.errors, ...(poolIdl.errors ?? [])], + } +} + +/** Burn-mint token pool IDL with shared base definitions. */ +export const TOKEN_POOL_IDL = composeTokenPoolIdl(BURN_MINT_TOKEN_POOL) + +/** Lock-release token pool IDL with shared base definitions. */ +export const LOCK_RELEASE_TOKEN_POOL_IDL = composeTokenPoolIdl(LOCK_RELEASE_TOKEN_POOL) + +/** Shared state configuration stored by canonical Solana token pools. */ +export type TokenPoolConfig = IdlTypes['BaseConfig'] + +/** Borsh decoder for burn-mint token pool instructions and canonical token pool accounts. */ +export const tokenPoolCoder = new BorshCoder(TOKEN_POOL_IDL) + +/** Borsh decoder for lock-release token pool instructions and canonical token pool accounts. */ +export const lockReleaseTokenPoolCoder = new BorshCoder(LOCK_RELEASE_TOKEN_POOL_IDL) diff --git a/ccip-sdk/src/solana/index.ts b/ccip-sdk/src/solana/index.ts index e631441b6..230cb3084 100644 --- a/ccip-sdk/src/solana/index.ts +++ b/ccip-sdk/src/solana/index.ts @@ -9,7 +9,6 @@ import { Connection, PublicKey, SYSVAR_CLOCK_PUBKEY, - SystemProgram, } from '@solana/web3.js' import BN from 'bn.js' import bs58 from 'bs58' @@ -57,7 +56,6 @@ import { CCIPSplTokenInvalidError, CCIPTokenAccountNotFoundError, CCIPTokenDataParseError, - CCIPTokenNotConfiguredError, CCIPTokenPoolChainConfigNotFoundError, CCIPTokenPoolStateNotFoundError, CCIPTopicsInvalidError, @@ -134,6 +132,10 @@ import { getTransactionsForAddress } from './logs.ts' import { patchBorsh } from './patchBorsh.ts' import { generateUnsignedCcipSend, getFee } from './send.ts' import { cacheGetSignaturesForAddress } from './signatures-cache.ts' +import { + decodeTokenAdminRegistryConfig, + getTokenAdminRegistryConfig, +} from './token-admin-registry.ts' import { type CCIPMessage_V1_6_Solana, type UnsignedSolanaTx, isWallet } from './types.ts' import { convertRateLimiter, @@ -1911,49 +1913,15 @@ export class SolanaChain extends Chain { pendingAdministrator?: string tokenPool?: string }> { - const registry_ = new PublicKey(registry) - const tokenMint = new PublicKey(token) - - const [tokenAdminRegistryAddr] = PublicKey.findProgramAddressSync( - [Buffer.from('token_admin_registry'), tokenMint.toBuffer()], - registry_, - ) - - const tokenAdminRegistry = await this.connection.getAccountInfo(tokenAdminRegistryAddr) - if (!tokenAdminRegistry) throw new CCIPTokenNotConfiguredError(token, registry) - - const config: { - administrator: string - pendingAdministrator?: string - tokenPool?: string - } = { - administrator: encodeBase58(tokenAdminRegistry.data.subarray(9, 9 + 32)), - } - const pendingAdministrator = new PublicKey(tokenAdminRegistry.data.subarray(41, 41 + 32)) - - // Check if pendingAdministrator is set (not system program address) - if ( - !pendingAdministrator.equals(SystemProgram.programId) && - !pendingAdministrator.equals(PublicKey.default) - ) { - config.pendingAdministrator = pendingAdministrator.toBase58() - } - - // Get token pool from lookup table if available - try { - const lookupTableAddr = new PublicKey(tokenAdminRegistry.data.subarray(73, 73 + 32)) - const lookupTable = await this.connection.getAddressLookupTable(lookupTableAddr) - if (lookupTable.value) { - // tokenPool state PDA is at index [3] - const tokenPoolAddress = lookupTable.value.state.addresses[3] - if (tokenPoolAddress && !tokenPoolAddress.equals(PublicKey.default)) { - config.tokenPool = tokenPoolAddress.toBase58() - } - } - } catch (_err) { - // Token pool may not be configured yet + const router = new PublicKey(registry) + const config = await getTokenAdminRegistryConfig(this.connection, router, new PublicKey(token)) + return { + administrator: config.administrator.toBase58(), + ...(config.pendingAdministrator && { + pendingAdministrator: config.pendingAdministrator.toBase58(), + }), + ...(config.tokenPool && { tokenPool: config.tokenPool.toBase58() }), } - return config } /** @@ -2109,8 +2077,6 @@ export class SolanaChain extends Chain { /** {@inheritDoc Chain.getSupportedTokens} */ async getSupportedTokens(router: string): Promise { - // `mint` offset in TokenAdminRegistry account data; more robust against changes in layout - const mintOffset = 8 + 1 + 32 + 32 + 32 + 16 * 2 // = 137 const router_ = new PublicKey(router) const res = [] for (const acc of await this.connection.getProgramAccounts(router_, { @@ -2123,14 +2089,16 @@ export class SolanaChain extends Chain { }, ], })) { - if (acc.account.data.length < mintOffset + 32) continue - const mint = new PublicKey(acc.account.data.subarray(mintOffset, mintOffset + 32)) - const [derivedPda] = PublicKey.findProgramAddressSync( - [Buffer.from('token_admin_registry'), mint.toBuffer()], - router_, - ) - if (!acc.pubkey.equals(derivedPda)) continue - res.push(mint.toBase58()) + try { + const { mint } = decodeTokenAdminRegistryConfig(acc.account.data) + const [derivedPda] = PublicKey.findProgramAddressSync( + [Buffer.from('token_admin_registry'), mint.toBuffer()], + router_, + ) + if (acc.pubkey.equals(derivedPda)) res.push(mint.toBase58()) + } catch { + // Skip malformed TokenAdminRegistry accounts. + } } return res } diff --git a/ccip-sdk/src/solana/token-admin-registry.ts b/ccip-sdk/src/solana/token-admin-registry.ts new file mode 100644 index 000000000..335ad272a --- /dev/null +++ b/ccip-sdk/src/solana/token-admin-registry.ts @@ -0,0 +1,103 @@ +import { Buffer } from 'buffer' + +import { BorshAccountsCoder } from '@coral-xyz/anchor' +import { type Connection, PublicKey, SystemProgram } from '@solana/web3.js' + +import { CCIPDataFormatUnsupportedError, CCIPTokenNotConfiguredError } from '../errors/index.ts' + +/** Decoded configuration stored in a Solana TokenAdminRegistry account. */ +export type TokenAdminRegistryConfig = { + mint: PublicKey + administrator: PublicKey + pendingAdministrator?: PublicKey + lookupTable?: PublicKey + tokenPool?: PublicKey + writableIndexes: number[] + supportsAutoDerivation: boolean +} + +const TOKEN_ADMIN_REGISTRY_DISCRIMINATOR = + BorshAccountsCoder.accountDiscriminator('TokenAdminRegistry') +const TOKEN_ADMIN_REGISTRY_SIZE = 169 + +/** Decodes the Router's 32-byte MSB-first writable-index bitmap. */ +function decodeWritableIndexes(buf: Buffer): number[] { + const indexes: number[] = [] + for (let byteIndex = 0; byteIndex < 32; byteIndex++) { + const byte = buf[byteIndex] ?? 0 + for (let bit = 0; bit < 8; bit++) { + if (byte & (1 << bit)) { + const bitPosition = (byteIndex % 16) * 8 + bit + indexes.push(byteIndex < 16 ? 127 - bitPosition : 255 - bitPosition) + } + } + } + return indexes.sort((a, b) => a - b) +} + +function isSet(address: PublicKey): boolean { + return !address.equals(PublicKey.default) && !address.equals(SystemProgram.programId) +} + +/** + * Decodes a TokenAdminRegistry account + * + * @param data - Raw TokenAdminRegistry account data. + * @returns Decoded registry configuration, excluding the resolved token pool. + */ +export function decodeTokenAdminRegistryConfig( + data: Buffer, +): Omit { + if ( + data.length < TOKEN_ADMIN_REGISTRY_SIZE || + !data.subarray(0, 8).equals(TOKEN_ADMIN_REGISTRY_DISCRIMINATOR) + ) { + throw new CCIPDataFormatUnsupportedError('invalid TokenAdminRegistry account data') + } + + const pendingAdministrator = new PublicKey(data.subarray(41, 73)) + const lookupTable = new PublicKey(data.subarray(73, 105)) + + return { + mint: new PublicKey(data.subarray(137, 169)), + administrator: new PublicKey(data.subarray(9, 41)), + ...(isSet(pendingAdministrator) && { pendingAdministrator }), + ...(isSet(lookupTable) && { lookupTable }), + writableIndexes: decodeWritableIndexes(data.subarray(105, 137)), + supportsAutoDerivation: data.length > TOKEN_ADMIN_REGISTRY_SIZE && data[169] === 1, + } +} + +/** + * Fetches and decodes a token's TokenAdminRegistry account. + * + * @param connection - Solana RPC connection. + * @param router - Router program that owns the registry account. + * @param mint - Token mint registered with the Router. + * @returns TokenAdminRegistryConfig - The decoded registry configuration. + */ +export async function getTokenAdminRegistryConfig( + connection: Connection, + router: PublicKey, + mint: PublicKey, +): Promise { + const registry = PublicKey.findProgramAddressSync( + [Buffer.from('token_admin_registry'), mint.toBuffer()], + router, + )[0] + + const account = await connection.getAccountInfo(registry) + if (!account) throw new CCIPTokenNotConfiguredError(mint.toBase58(), router.toBase58()) + + const config = decodeTokenAdminRegistryConfig(account.data) + if (!config.lookupTable) return config + + try { + const lookupTable = await connection.getAddressLookupTable(config.lookupTable) + const tokenPool = lookupTable.value?.state.addresses[3] + if (tokenPool && !tokenPool.equals(PublicKey.default)) return { ...config, tokenPool } + } catch { + // Token pool may not be configured yet. + } + return config +} diff --git a/ccip-sdk/src/solana/utils.ts b/ccip-sdk/src/solana/utils.ts index 5c5eb5ae0..b30e86dcb 100644 --- a/ccip-sdk/src/solana/utils.ts +++ b/ccip-sdk/src/solana/utils.ts @@ -46,6 +46,58 @@ export type ResolvedATA = { mintInfo: AccountInfo } +/** + * Fetches and validates a token mint account. + * + * @param connection - Solana connection instance. + * @param mint - Token mint address. + * @returns The validated mint account info. + * @throws CCIPTokenMintNotFoundError If the mint account does not exist. + * @throws CCIPTokenMintInvalidError If the mint is not owned by an SPL Token program. + * + * @example + * ```ts + * const mintInfo = await resolveTokenMint(connection, mint) + * ``` + */ +export async function resolveTokenMint( + connection: Connection, + mint: PublicKey, +): Promise> { + const mintInfo = await connection.getAccountInfo(mint) + if (!mintInfo) throw new CCIPTokenMintNotFoundError(mint.toBase58()) + + if (!mintInfo.owner.equals(TOKEN_PROGRAM_ID) && !mintInfo.owner.equals(TOKEN_2022_PROGRAM_ID)) { + throw new CCIPTokenMintInvalidError(mint.toBase58(), mintInfo.owner.toBase58(), [ + TOKEN_PROGRAM_ID.toBase58(), + TOKEN_2022_PROGRAM_ID.toBase58(), + ]) + } + + return mintInfo +} + +/** + * Resolves and validates the SPL Token program that owns a mint. + * + * @param connection - Solana connection instance. + * @param mint - Token mint address. + * @returns The SPL Token or Token-2022 program address that owns the mint. + * @throws CCIPTokenMintNotFoundError If the mint account does not exist. + * @throws CCIPTokenMintInvalidError If the mint is not owned by an SPL Token program. + * + * @example + * ```ts + * const tokenProgram = await resolveTokenProgram(connection, mint) + * ``` + */ +export async function resolveTokenProgram( + connection: Connection, + mint: PublicKey, +): Promise { + return (await resolveTokenMint(connection, mint)).owner +} + /** * Resolves the Associated Token Account (ATA) for a given mint and owner. * Automatically detects the correct token program (SPL Token vs Token-2022). @@ -67,22 +119,7 @@ export async function resolveATA( mint: PublicKey, owner: PublicKey, ): Promise { - const mintInfo = await connection.getAccountInfo(mint) - if (!mintInfo) { - throw new CCIPTokenMintNotFoundError(mint.toBase58()) - } - - // Validate the mint is owned by a valid token program - const isValidTokenProgram = - mintInfo.owner.equals(TOKEN_PROGRAM_ID) || mintInfo.owner.equals(TOKEN_2022_PROGRAM_ID) - - if (!isValidTokenProgram) { - throw new CCIPTokenMintInvalidError(mint.toBase58(), mintInfo.owner.toBase58(), [ - TOKEN_PROGRAM_ID.toBase58(), - TOKEN_2022_PROGRAM_ID.toBase58(), - ]) - } - + const mintInfo = await resolveTokenMint(connection, mint) // Allow PDAs as owners (for program vaults, etc.) const ata = getAssociatedTokenAddressSync(mint, owner, true, mintInfo.owner) return { diff --git a/ccip-sdk/tsconfig.build.json b/ccip-sdk/tsconfig.build.json index 8a845f9f0..78779ee5b 100644 --- a/ccip-sdk/tsconfig.build.json +++ b/ccip-sdk/tsconfig.build.json @@ -4,13 +4,6 @@ "outDir": "./dist", "rootDir": "./src" }, - "include": [ - "./src" - ], - "exclude": [ - "node_modules", - "**/*.test.*", - "**/__tests__", - "**/__mocks__" - ] + "include": ["./src"], + "exclude": ["node_modules", "**/*.test.*", "**/__tests__", "**/__mocks__"] } diff --git a/package-lock.json b/package-lock.json index 3435d0ade..b042817c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "ccip-api-ref" ], "devDependencies": { + "@chainlink/contracts-ccip": "2.0.0", "@types/node": "26.4.1", "@typescript/native": "npm:typescript@7.0.2", "brace-expansion": "5.0.9", @@ -59,27 +60,6 @@ "node": ">=20.0.0" } }, - "ccip-api-ref/node_modules/react": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", - "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "ccip-api-ref/node_modules/react-dom": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", - "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.8" - } - }, "ccip-cli": { "name": "@chainlink/ccip-cli", "version": "1.13.1", @@ -117,8 +97,6 @@ }, "ccip-cli/node_modules/typescript": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", - "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -157,6 +135,10 @@ "dependencies": { "@aptos-labs/ts-sdk": "^7.3.0", "@coral-xyz/anchor": "^0.29.0", + "@metaplex-foundation/mpl-token-metadata": "3.4.0", + "@metaplex-foundation/umi": "1.5.1", + "@metaplex-foundation/umi-bundle-defaults": "1.5.1", + "@metaplex-foundation/umi-web3js-adapters": "1.5.1", "@mysten/bcs": "^2.1.0", "@mysten/sui": "^2.23.2", "@noble/hashes": "^2.3.0", @@ -195,8 +177,6 @@ }, "ccip-sdk/node_modules/typescript": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", - "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -230,8 +210,6 @@ }, "node_modules/@0no-co/graphql.web": { "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@0no-co/graphql.web/-/graphql.web-1.3.4.tgz", - "integrity": "sha512-imSwulOeDQodRy/olQmVEo2PiY6ntjkZ9eiGdw6lMYylh/tay9b7MusyJBmEnkL8GiKRKr6ltr+D42mY5bd8Bg==", "license": "MIT", "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" @@ -244,8 +222,6 @@ }, "node_modules/@0no-co/graphqlsp": { "version": "1.17.5", - "resolved": "https://registry.npmjs.org/@0no-co/graphqlsp/-/graphqlsp-1.17.5.tgz", - "integrity": "sha512-wsygfTO3pdkz4idDw8BxdMdgB9AB8nuJDJTUwYy87BiZEZRXS4bEfn4oNl9VtxTo6j2lzX/gED+IvOgxsSy2Jg==", "license": "MIT", "dependencies": { "@gql.tada/internal": "^1.2.1", @@ -258,8 +234,6 @@ }, "node_modules/@11ty/gray-matter": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@11ty/gray-matter/-/gray-matter-1.0.0.tgz", - "integrity": "sha512-7mJJl+wf1AByoT0PknQiQfOPnVNT4fevGrUBVWO4HXsnYn1aQPyRyrELYrNUFleUBM++KzMKN6QaxHPk0t/6/g==", "license": "MIT", "dependencies": { "js-yaml": "^4.1.0", @@ -273,14 +247,10 @@ }, "node_modules/@adraffy/ens-normalize": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", - "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", "license": "MIT" }, "node_modules/@algolia/abtesting": { "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.23.0.tgz", - "integrity": "sha512-j45MBISstltys9QyQ4xf6quRiN1g7vMuwQL9VM4dx8YuRZvCQ173b9royZAx6iAbRX3IB1VnG1z//NuwyQ8jpQ==", "license": "MIT", "dependencies": { "@algolia/client-common": "5.57.0", @@ -294,8 +264,6 @@ }, "node_modules/@algolia/autocomplete-core": { "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.9.tgz", - "integrity": "sha512-4U2JKLMWlDu0CotYyUkWakDxr8AIav3QtIUXXRpfavYN29aVWfzlwJp9T0rPKEf/dO2QCPAUc0Kq1Tj1GJxo2A==", "license": "MIT", "dependencies": { "@algolia/autocomplete-plugin-algolia-insights": "1.19.9", @@ -304,8 +272,6 @@ }, "node_modules/@algolia/autocomplete-plugin-algolia-insights": { "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.9.tgz", - "integrity": "sha512-6mExC6X7762s2SV3eJy3QOkB8bdMmnUhQ2agvGVDuzwoGyr3PquGSY/0vPQXCfiAiCaXUz1rXn+lwghgSi0l0w==", "license": "MIT", "dependencies": { "@algolia/autocomplete-shared": "1.19.9" @@ -316,8 +282,6 @@ }, "node_modules/@algolia/autocomplete-shared": { "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.9.tgz", - "integrity": "sha512-YosP9Uoek6y/Ur1r1qeogk4biMe/hzkyNcgMCciw0//3XpCM7VlYLSHnyt/vOnEOGhCCc0+3v+unEiH6zz+Z1A==", "license": "MIT", "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -326,8 +290,6 @@ }, "node_modules/@algolia/client-abtesting": { "version": "5.57.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.57.0.tgz", - "integrity": "sha512-JVFFujiZUCguk5tz3LZr4fTQxqpIrj4/Jw3SI7kMljSqtfLxYn/s/TWH0J2s4iNfsDpxPhgFGMotCpmDI4kZ8w==", "license": "MIT", "dependencies": { "@algolia/client-common": "5.57.0", @@ -341,8 +303,6 @@ }, "node_modules/@algolia/client-analytics": { "version": "5.57.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.57.0.tgz", - "integrity": "sha512-6KqECK4ED3JJQEoDrQWnGPQzElA828xAD4qK5ceawNNyP/LcSvzAoLHjFkoTPksZ/kxj6VUtCRH+IHZesLltng==", "license": "MIT", "dependencies": { "@algolia/client-common": "5.57.0", @@ -356,8 +316,6 @@ }, "node_modules/@algolia/client-common": { "version": "5.57.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.57.0.tgz", - "integrity": "sha512-uqpGF3oXYsoCbQq5d7BzNrNTfIfuvJyGP1CKvSW27T9boUg7KOwyxsAw1AX0a3jSW2HrYEJ/NN+Z4MiGivbpeQ==", "license": "MIT", "engines": { "node": ">= 14.0.0" @@ -365,8 +323,6 @@ }, "node_modules/@algolia/client-insights": { "version": "5.57.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.57.0.tgz", - "integrity": "sha512-u5NboJVJXDEFplvNnqqX4CxkXPYysjJRj47hOSh9329H8kG5gFLKJBIiS5utMQ+GZm8xQl3Te7NInDk6elEADQ==", "license": "MIT", "dependencies": { "@algolia/client-common": "5.57.0", @@ -380,8 +336,6 @@ }, "node_modules/@algolia/client-personalization": { "version": "5.57.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.57.0.tgz", - "integrity": "sha512-uzc0b2LmHAK9/QID4xeo35OG84AkZl4YewkCqawqAOGLjT2eZpM/OZx45ESygMHG30Ws+ZTSdluPtMJcUnrbWQ==", "license": "MIT", "dependencies": { "@algolia/client-common": "5.57.0", @@ -395,8 +349,6 @@ }, "node_modules/@algolia/client-query-suggestions": { "version": "5.57.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.57.0.tgz", - "integrity": "sha512-dIAhnM6ue/ssa5PjgNfu4g8A4yTojl9ZOUzZU3wIaIKRerL2R/3Emuf9n/D6ICXXP167KC6XCeC7nliSw7cuSw==", "license": "MIT", "dependencies": { "@algolia/client-common": "5.57.0", @@ -410,9 +362,8 @@ }, "node_modules/@algolia/client-search": { "version": "5.57.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.57.0.tgz", - "integrity": "sha512-2TTPTTKSJmCptvhCm4Xf3bBYMqZni+Pgc2hVdqc4l9wsBpSJNVTVIKpnd10OubUgkGcmppVDj1XQqYaf6EnPSQ==", "license": "MIT", + "peer": true, "dependencies": { "@algolia/client-common": "5.57.0", "@algolia/requester-browser-xhr": "5.57.0", @@ -425,14 +376,10 @@ }, "node_modules/@algolia/events": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", - "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==", "license": "MIT" }, "node_modules/@algolia/ingestion": { "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.57.0.tgz", - "integrity": "sha512-W4JseHKt+pzOxlFV+T3MWEG0h4Z2Se5zjoXUD0ewlw8aOWMG/yjRdopUdLQsXULepB/My2tDuZjkwk2sMfsUrQ==", "license": "MIT", "dependencies": { "@algolia/client-common": "5.57.0", @@ -446,8 +393,6 @@ }, "node_modules/@algolia/monitoring": { "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.57.0.tgz", - "integrity": "sha512-BrxJVE0/eLinEPICCD7BKN/2xnt0nkjge70u8zzE2ISP3fuB3tjLgcwpanUycvlHBFLI4gK0l5ol54p6IYuR/Q==", "license": "MIT", "dependencies": { "@algolia/client-common": "5.57.0", @@ -461,8 +406,6 @@ }, "node_modules/@algolia/recommend": { "version": "5.57.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.57.0.tgz", - "integrity": "sha512-Gc29jkeiLKlVfHvyrIgyUHHE+aYTdXEeLfK42rjr5/1TTVsYwUJz0XkvoIBIqfMjcDg6gXeHb1jTUZ0H+SYIlQ==", "license": "MIT", "dependencies": { "@algolia/client-common": "5.57.0", @@ -476,8 +419,6 @@ }, "node_modules/@algolia/requester-browser-xhr": { "version": "5.57.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.57.0.tgz", - "integrity": "sha512-PIPnPN7MP3fp2VAi01BVXhCWmD366ZB2Hkq5TlYKtThd4KxUtMmaaNDpFgVCTXtSIqWVZLJntOHRvxg/sIPd8Q==", "license": "MIT", "dependencies": { "@algolia/client-common": "5.57.0" @@ -488,8 +429,6 @@ }, "node_modules/@algolia/requester-fetch": { "version": "5.57.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.57.0.tgz", - "integrity": "sha512-AX3RlOudXMdTwtwUqdAf5hAVLvXfOZZH1FZh6ALDdrhVLT0TtAIe48N6nYcWcTnwoTxK/wDIQqZ19IMjK8zJAA==", "license": "MIT", "dependencies": { "@algolia/client-common": "5.57.0" @@ -500,8 +439,6 @@ }, "node_modules/@algolia/requester-node-http": { "version": "5.57.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.57.0.tgz", - "integrity": "sha512-cWZc1dKb7wy9/wPpwMtL1y89gK2G7y2A47Coa7zwf1ydtIeJm4+S+XxoQ2b/ZRiQnrC1YHavLjYUPDCdnT7Khg==", "license": "MIT", "dependencies": { "@algolia/client-common": "5.57.0" @@ -511,9 +448,7 @@ } }, "node_modules/@alloc/quick-lru": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.3.0.tgz", - "integrity": "sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==", + "version": "5.2.0", "license": "MIT", "engines": { "node": ">=10" @@ -524,8 +459,6 @@ }, "node_modules/@antfu/install-pkg": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", - "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", "license": "MIT", "dependencies": { "package-manager-detector": "^1.3.0", @@ -537,8 +470,6 @@ }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-15.5.2.tgz", - "integrity": "sha512-B+C9Ok0DF/rjANIUHgwcV5/d4C72MB7f2IbKFL8jDGcGq2qn3yr893s8vAn2kbhmyaelAin0JK8EKF9P1+y7aQ==", "license": "MIT", "dependencies": { "js-yaml": "^5.2.2", @@ -553,8 +484,6 @@ }, "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { "version": "5.2.3", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.3.tgz", - "integrity": "sha512-n+mUVyUX5bVv7G/G2zyIHOhdxfuU1dY2NOFzTQUWiMUbFss8b57NFlgCCaggU78wSw5KVS9cllzeLyzyR+n5nw==", "funding": [ { "type": "github", @@ -678,6 +607,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@aptos-labs/ts-sdk/node_modules/@scure/base": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.4.0.tgz", + "integrity": "sha512-thZ1TuJwFwBblOhgsjDKvvGirBxNp+wSvY/DR6tJBJOTDhdAAcHJ8Vbr2eFnqaxeca4+t0i9KBf+uHYGWwZORg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@aptos-labs/ts-sdk/node_modules/@scure/bip32": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-2.4.0.tgz", @@ -743,10 +681,26 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@arbitrum/nitro-contracts": { + "version": "3.0.0", + "dev": true, + "hasInstallScript": true, + "license": "BUSL-1.1", + "dependencies": { + "@offchainlabs/upgrade-executor": "1.1.0-beta.0", + "@openzeppelin/contracts": "4.7.3", + "@openzeppelin/contracts-upgradeable": "4.7.3", + "patch-package": "^6.4.7", + "solady": "0.0.182" + } + }, + "node_modules/@arbitrum/nitro-contracts/node_modules/@openzeppelin/contracts-upgradeable": { + "version": "4.7.3", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/code-frame": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", @@ -759,8 +713,6 @@ }, "node_modules/@babel/compat-data": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -768,9 +720,8 @@ }, "node_modules/@babel/core": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -798,8 +749,6 @@ }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -807,8 +756,6 @@ }, "node_modules/@babel/generator": { "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", - "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "license": "MIT", "dependencies": { "@babel/parser": "^7.29.8", @@ -823,8 +770,6 @@ }, "node_modules/@babel/helper-annotate-as-pure": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", - "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "license": "MIT", "dependencies": { "@babel/types": "^7.29.7" @@ -835,8 +780,6 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "license": "MIT", "dependencies": { "@babel/compat-data": "^7.29.7", @@ -849,19 +792,26 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { "semver": "bin/semver.js" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "license": "ISC" + }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", - "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", @@ -881,8 +831,6 @@ }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -890,8 +838,6 @@ }, "node_modules/@babel/helper-create-regexp-features-plugin": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz", - "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", @@ -907,8 +853,6 @@ }, "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -916,8 +860,6 @@ }, "node_modules/@babel/helper-define-polyfill-provider": { "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", - "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", @@ -932,8 +874,6 @@ }, "node_modules/@babel/helper-globals": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -941,8 +881,6 @@ }, "node_modules/@babel/helper-member-expression-to-functions": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", - "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", "license": "MIT", "dependencies": { "@babel/traverse": "^7.29.7", @@ -954,8 +892,6 @@ }, "node_modules/@babel/helper-module-imports": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "license": "MIT", "dependencies": { "@babel/traverse": "^7.29.7", @@ -967,8 +903,6 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.29.7", @@ -984,8 +918,6 @@ }, "node_modules/@babel/helper-optimise-call-expression": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", - "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", "license": "MIT", "dependencies": { "@babel/types": "^7.29.7" @@ -996,8 +928,6 @@ }, "node_modules/@babel/helper-plugin-utils": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", - "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1005,8 +935,6 @@ }, "node_modules/@babel/helper-remap-async-to-generator": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", - "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", @@ -1022,8 +950,6 @@ }, "node_modules/@babel/helper-replace-supers": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", - "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", "license": "MIT", "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", @@ -1039,8 +965,6 @@ }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", - "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "license": "MIT", "dependencies": { "@babel/traverse": "^7.29.7", @@ -1052,8 +976,6 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1061,8 +983,6 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1070,8 +990,6 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1079,8 +997,6 @@ }, "node_modules/@babel/helper-wrap-function": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", - "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", "license": "MIT", "dependencies": { "@babel/template": "^7.29.7", @@ -1093,8 +1009,6 @@ }, "node_modules/@babel/helpers": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "license": "MIT", "dependencies": { "@babel/template": "^7.29.7", @@ -1106,8 +1020,6 @@ }, "node_modules/@babel/parser": { "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", - "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "license": "MIT", "dependencies": { "@babel/types": "^7.29.8" @@ -1121,8 +1033,6 @@ }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.29.7.tgz", - "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", @@ -1137,8 +1047,6 @@ }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.29.7.tgz", - "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1152,8 +1060,6 @@ }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.29.7.tgz", - "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1167,8 +1073,6 @@ }, "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.7.tgz", - "integrity": "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", @@ -1183,8 +1087,6 @@ }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.29.7.tgz", - "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", @@ -1200,8 +1102,6 @@ }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.29.7.tgz", - "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", @@ -1216,8 +1116,6 @@ }, "node_modules/@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1228,8 +1126,6 @@ }, "node_modules/@babel/plugin-syntax-dynamic-import": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -1240,8 +1136,6 @@ }, "node_modules/@babel/plugin-syntax-import-assertions": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz", - "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1255,8 +1149,6 @@ }, "node_modules/@babel/plugin-syntax-import-attributes": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", - "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1270,8 +1162,6 @@ }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", - "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1285,8 +1175,6 @@ }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", - "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1300,8 +1188,6 @@ }, "node_modules/@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", @@ -1316,8 +1202,6 @@ }, "node_modules/@babel/plugin-transform-arrow-functions": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", - "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1331,8 +1215,6 @@ }, "node_modules/@babel/plugin-transform-async-generator-functions": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", - "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", @@ -1348,8 +1230,6 @@ }, "node_modules/@babel/plugin-transform-async-to-generator": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", - "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.29.7", @@ -1365,8 +1245,6 @@ }, "node_modules/@babel/plugin-transform-block-scoped-functions": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.29.7.tgz", - "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1380,8 +1258,6 @@ }, "node_modules/@babel/plugin-transform-block-scoping": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", - "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1395,8 +1271,6 @@ }, "node_modules/@babel/plugin-transform-class-properties": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", - "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", @@ -1411,8 +1285,6 @@ }, "node_modules/@babel/plugin-transform-class-static-block": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", - "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", @@ -1427,8 +1299,6 @@ }, "node_modules/@babel/plugin-transform-classes": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", - "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", @@ -1447,8 +1317,6 @@ }, "node_modules/@babel/plugin-transform-computed-properties": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz", - "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", @@ -1463,8 +1331,6 @@ }, "node_modules/@babel/plugin-transform-destructuring": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", - "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", @@ -1479,8 +1345,6 @@ }, "node_modules/@babel/plugin-transform-dotall-regex": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.29.7.tgz", - "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==", "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", @@ -1495,8 +1359,6 @@ }, "node_modules/@babel/plugin-transform-duplicate-keys": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.29.7.tgz", - "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1510,8 +1372,6 @@ }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.7.tgz", - "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==", "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", @@ -1526,8 +1386,6 @@ }, "node_modules/@babel/plugin-transform-dynamic-import": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.29.7.tgz", - "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1541,8 +1399,6 @@ }, "node_modules/@babel/plugin-transform-explicit-resource-management": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz", - "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", @@ -1557,8 +1413,6 @@ }, "node_modules/@babel/plugin-transform-exponentiation-operator": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.29.7.tgz", - "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1572,8 +1426,6 @@ }, "node_modules/@babel/plugin-transform-export-namespace-from": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", - "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1587,8 +1439,6 @@ }, "node_modules/@babel/plugin-transform-for-of": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", - "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", @@ -1603,8 +1453,6 @@ }, "node_modules/@babel/plugin-transform-function-name": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz", - "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.29.7", @@ -1620,8 +1468,6 @@ }, "node_modules/@babel/plugin-transform-json-strings": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.29.7.tgz", - "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1635,8 +1481,6 @@ }, "node_modules/@babel/plugin-transform-literals": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz", - "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1650,8 +1494,6 @@ }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", - "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1665,8 +1507,6 @@ }, "node_modules/@babel/plugin-transform-member-expression-literals": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.29.7.tgz", - "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1680,8 +1520,6 @@ }, "node_modules/@babel/plugin-transform-modules-amd": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.29.7.tgz", - "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==", "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.29.7", @@ -1696,8 +1534,6 @@ }, "node_modules/@babel/plugin-transform-modules-commonjs": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", - "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.29.7", @@ -1712,8 +1548,6 @@ }, "node_modules/@babel/plugin-transform-modules-systemjs": { "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.8.tgz", - "integrity": "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==", "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.29.7", @@ -1730,8 +1564,6 @@ }, "node_modules/@babel/plugin-transform-modules-umd": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.29.7.tgz", - "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==", "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.29.7", @@ -1746,8 +1578,6 @@ }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", - "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", @@ -1762,8 +1592,6 @@ }, "node_modules/@babel/plugin-transform-new-target": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.29.7.tgz", - "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1777,8 +1605,6 @@ }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", - "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1792,8 +1618,6 @@ }, "node_modules/@babel/plugin-transform-numeric-separator": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz", - "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1807,8 +1631,6 @@ }, "node_modules/@babel/plugin-transform-object-rest-spread": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", - "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.29.7", @@ -1826,8 +1648,6 @@ }, "node_modules/@babel/plugin-transform-object-super": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.29.7.tgz", - "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", @@ -1842,8 +1662,6 @@ }, "node_modules/@babel/plugin-transform-optional-catch-binding": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", - "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1857,8 +1675,6 @@ }, "node_modules/@babel/plugin-transform-optional-chaining": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", - "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", @@ -1873,8 +1689,6 @@ }, "node_modules/@babel/plugin-transform-parameters": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", - "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1888,8 +1702,6 @@ }, "node_modules/@babel/plugin-transform-private-methods": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", - "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", @@ -1904,8 +1716,6 @@ }, "node_modules/@babel/plugin-transform-private-property-in-object": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", - "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", @@ -1921,8 +1731,6 @@ }, "node_modules/@babel/plugin-transform-property-literals": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.29.7.tgz", - "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1936,8 +1744,6 @@ }, "node_modules/@babel/plugin-transform-react-constant-elements": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.29.7.tgz", - "integrity": "sha512-J0wGhKan+rIiE2OhfhRptySLrJ6SjQYM6b6N1FMlhyhCcw1Mig8vQjWchyB+bgHGDvaWo6Diu6CLRMra2uMtmg==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1951,8 +1757,6 @@ }, "node_modules/@babel/plugin-transform-react-display-name": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz", - "integrity": "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1966,8 +1770,6 @@ }, "node_modules/@babel/plugin-transform-react-jsx": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz", - "integrity": "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", @@ -1985,8 +1787,6 @@ }, "node_modules/@babel/plugin-transform-react-jsx-development": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.29.7.tgz", - "integrity": "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==", "license": "MIT", "dependencies": { "@babel/plugin-transform-react-jsx": "^7.29.7" @@ -2000,8 +1800,6 @@ }, "node_modules/@babel/plugin-transform-react-pure-annotations": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz", - "integrity": "sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", @@ -2016,8 +1814,6 @@ }, "node_modules/@babel/plugin-transform-regenerator": { "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.8.tgz", - "integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -2031,8 +1827,6 @@ }, "node_modules/@babel/plugin-transform-regexp-modifiers": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.29.7.tgz", - "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==", "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", @@ -2047,8 +1841,6 @@ }, "node_modules/@babel/plugin-transform-reserved-words": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.29.7.tgz", - "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -2062,8 +1854,6 @@ }, "node_modules/@babel/plugin-transform-runtime": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.7.tgz", - "integrity": "sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==", "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.29.7", @@ -2082,8 +1872,6 @@ }, "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2091,8 +1879,6 @@ }, "node_modules/@babel/plugin-transform-shorthand-properties": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", - "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -2106,8 +1892,6 @@ }, "node_modules/@babel/plugin-transform-spread": { "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.8.tgz", - "integrity": "sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", @@ -2122,8 +1906,6 @@ }, "node_modules/@babel/plugin-transform-sticky-regex": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz", - "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -2137,8 +1919,6 @@ }, "node_modules/@babel/plugin-transform-template-literals": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz", - "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -2152,8 +1932,6 @@ }, "node_modules/@babel/plugin-transform-typeof-symbol": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.29.7.tgz", - "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -2167,8 +1945,6 @@ }, "node_modules/@babel/plugin-transform-typescript": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", - "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", @@ -2186,8 +1962,6 @@ }, "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz", - "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -2201,8 +1975,6 @@ }, "node_modules/@babel/plugin-transform-unicode-property-regex": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.29.7.tgz", - "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==", "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", @@ -2217,8 +1989,6 @@ }, "node_modules/@babel/plugin-transform-unicode-regex": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", - "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", @@ -2233,8 +2003,6 @@ }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.29.7.tgz", - "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==", "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", @@ -2249,8 +2017,6 @@ }, "node_modules/@babel/preset-env": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.7.tgz", - "integrity": "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==", "license": "MIT", "dependencies": { "@babel/compat-data": "^7.29.7", @@ -2334,8 +2100,6 @@ }, "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { "version": "0.14.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", - "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", "license": "MIT", "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8", @@ -2347,8 +2111,6 @@ }, "node_modules/@babel/preset-env/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2356,8 +2118,6 @@ }, "node_modules/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", @@ -2370,8 +2130,6 @@ }, "node_modules/@babel/preset-react": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.29.7.tgz", - "integrity": "sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", @@ -2390,8 +2148,6 @@ }, "node_modules/@babel/preset-typescript": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", - "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", @@ -2409,8 +2165,6 @@ }, "node_modules/@babel/runtime": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", - "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2418,8 +2172,6 @@ }, "node_modules/@babel/template": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -2432,8 +2184,6 @@ }, "node_modules/@babel/traverse": { "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", - "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -2450,8 +2200,6 @@ }, "node_modules/@babel/types": { "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", - "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -2463,8 +2211,6 @@ }, "node_modules/@bcoe/v8-coverage": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, "license": "MIT", "engines": { @@ -2473,10 +2219,13 @@ }, "node_modules/@braintree/sanitize-url": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", - "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", "license": "MIT" }, + "node_modules/@chainlink/ace": { + "version": "1.0.0", + "dev": true, + "license": "BUSL-1.1" + }, "node_modules/@chainlink/ccip-api-ref": { "resolved": "ccip-api-ref", "link": true @@ -2489,10 +2238,109 @@ "resolved": "ccip-sdk", "link": true }, + "node_modules/@chainlink/contracts": { + "version": "1.5.0", + "dev": true, + "license": "BUSL-1.1", + "dependencies": { + "@arbitrum/nitro-contracts": "3.0.0", + "@changesets/cli": "^2.29.6", + "@changesets/get-github-info": "^0.6.0", + "@eslint/eslintrc": "^3.3.1", + "@eth-optimism/contracts": "0.6.0", + "@openzeppelin/contracts-4.7.3": "npm:@openzeppelin/contracts@4.7.3", + "@openzeppelin/contracts-4.8.3": "npm:@openzeppelin/contracts@4.8.3", + "@openzeppelin/contracts-4.9.6": "npm:@openzeppelin/contracts@4.9.6", + "@openzeppelin/contracts-5.0.2": "npm:@openzeppelin/contracts@5.0.2", + "@openzeppelin/contracts-5.1.0": "npm:@openzeppelin/contracts@5.1.0", + "@openzeppelin/contracts-upgradeable": "4.9.6", + "@scroll-tech/contracts": "2.0.0", + "@zksync/contracts": "github:matter-labs/era-contracts#446d391d34bdb48255d5f8fef8a8248925fc98b9", + "semver": "^7.7.2" + }, + "engines": { + "node": ">=22", + "pnpm": ">=10" + } + }, + "node_modules/@chainlink/contracts-ccip": { + "version": "2.0.0", + "dev": true, + "license": "BUSL-1.1", + "dependencies": { + "@chainlink/ace": "1.0.0", + "@chainlink/contracts": "1.5.0", + "@openzeppelin/contracts-4.8.3": "npm:@openzeppelin/contracts@4.8.3", + "@openzeppelin/contracts-5.3.0": "npm:@openzeppelin/contracts@5.3.0" + }, + "engines": { + "node": ">=20", + "pnpm": ">=10" + } + }, + "node_modules/@chainlink/contracts/node_modules/@eth-optimism/contracts": { + "version": "0.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eth-optimism/core-utils": "0.12.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0" + }, + "peerDependencies": { + "ethers": "^5" + } + }, + "node_modules/@chainlink/contracts/node_modules/ethers": { + "version": "5.8.0", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@ethersproject/abi": "5.8.0", + "@ethersproject/abstract-provider": "5.8.0", + "@ethersproject/abstract-signer": "5.8.0", + "@ethersproject/address": "5.8.0", + "@ethersproject/base64": "5.8.0", + "@ethersproject/basex": "5.8.0", + "@ethersproject/bignumber": "5.8.0", + "@ethersproject/bytes": "5.8.0", + "@ethersproject/constants": "5.8.0", + "@ethersproject/contracts": "5.8.0", + "@ethersproject/hash": "5.8.0", + "@ethersproject/hdnode": "5.8.0", + "@ethersproject/json-wallets": "5.8.0", + "@ethersproject/keccak256": "5.8.0", + "@ethersproject/logger": "5.8.0", + "@ethersproject/networks": "5.8.0", + "@ethersproject/pbkdf2": "5.8.0", + "@ethersproject/properties": "5.8.0", + "@ethersproject/providers": "5.8.0", + "@ethersproject/random": "5.8.0", + "@ethersproject/rlp": "5.8.0", + "@ethersproject/sha2": "5.8.0", + "@ethersproject/signing-key": "5.8.0", + "@ethersproject/solidity": "5.8.0", + "@ethersproject/strings": "5.8.0", + "@ethersproject/transactions": "5.8.0", + "@ethersproject/units": "5.8.0", + "@ethersproject/wallet": "5.8.0", + "@ethersproject/web": "5.8.0", + "@ethersproject/wordlists": "5.8.0" + } + }, "node_modules/@chainlink/design-system": { "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@chainlink/design-system/-/design-system-0.2.8.tgz", - "integrity": "sha512-fi5t/EpwpLR3ZItYFynMW1PIuoW37+CUTa9FIr4n7XH+aa1A43m+uwTildZHrvkF0B45c7Lb065KyRRnFR1Q/Q==", "dependencies": { "@tailwindcss/container-queries": "0.1.1", "postcss": "8.4.38", @@ -2500,465 +2348,525 @@ "tailwindcss-animate": "1.0.7" } }, - "node_modules/@chevrotain/types": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", - "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", - "license": "Apache-2.0" - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "node_modules/@changesets/apply-release-plan": { + "version": "7.1.1", + "dev": true, "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" + "dependencies": { + "@changesets/config": "^3.1.4", + "@changesets/get-version-range-type": "^0.4.0", + "@changesets/git": "^3.0.4", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "detect-indent": "^6.0.0", + "fs-extra": "^7.0.1", + "lodash.startcase": "^4.4.0", + "outdent": "^0.5.0", + "prettier": "^2.7.1", + "resolve-from": "^5.0.0", + "semver": "^7.5.3" } }, - "node_modules/@coral-xyz/anchor": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.29.0.tgz", - "integrity": "sha512-eny6QNG0WOwqV0zQ7cs/b1tIuzZGmP7U7EcH+ogt4Gdbl8HDmIYVMh/9aTmYZPaFWjtUaI8qSn73uYEXWfATdA==", - "license": "(MIT OR Apache-2.0)", + "node_modules/@changesets/apply-release-plan/node_modules/fs-extra": { + "version": "7.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "@coral-xyz/borsh": "^0.29.0", - "@noble/hashes": "^1.3.1", - "@solana/web3.js": "^1.68.0", - "bn.js": "^5.1.2", - "bs58": "^4.0.1", - "buffer-layout": "^1.2.2", - "camelcase": "^6.3.0", - "cross-fetch": "^3.1.5", - "crypto-hash": "^1.3.0", - "eventemitter3": "^4.0.7", - "pako": "^2.0.3", - "snake-case": "^3.0.4", - "superstruct": "^0.15.4", - "toml": "^3.0.0" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, "engines": { - "node": ">=11" + "node": ">=6 <7 || >=8" } }, - "node_modules/@coral-xyz/anchor/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "node_modules/@changesets/apply-release-plan/node_modules/jsonfile": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@changesets/apply-release-plan/node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, "license": "MIT", "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": ">=8" } }, - "node_modules/@coral-xyz/anchor/node_modules/base-x": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", - "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "node_modules/@changesets/apply-release-plan/node_modules/universalify": { + "version": "0.1.2", + "dev": true, "license": "MIT", - "dependencies": { - "safe-buffer": "^5.0.1" + "engines": { + "node": ">= 4.0.0" } }, - "node_modules/@coral-xyz/anchor/node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "node_modules/@changesets/assemble-release-plan": { + "version": "6.0.10", + "dev": true, "license": "MIT", "dependencies": { - "base-x": "^3.0.2" + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.4", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "semver": "^7.5.3" } }, - "node_modules/@coral-xyz/anchor/node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" - }, - "node_modules/@coral-xyz/borsh": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.29.0.tgz", - "integrity": "sha512-s7VFVa3a0oqpkuRloWVPdCK7hMbAMY270geZOGfCnaqexrP5dTIpbEHL33req6IYPPJ0hYa71cdvJ1h6V55/oQ==", - "license": "Apache-2.0", + "node_modules/@changesets/changelog-git": { + "version": "0.2.1", + "dev": true, + "license": "MIT", "dependencies": { - "bn.js": "^5.1.2", - "buffer-layout": "^1.2.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@solana/web3.js": "^1.68.0" + "@changesets/types": "^6.1.0" } }, - "node_modules/@csstools/cascade-layer-name-parser": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz", - "integrity": "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], + "node_modules/@changesets/cli": { + "version": "2.31.1", + "dev": true, "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "@changesets/apply-release-plan": "^7.1.1", + "@changesets/assemble-release-plan": "^6.0.10", + "@changesets/changelog-git": "^0.2.1", + "@changesets/config": "^3.1.4", + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.4", + "@changesets/get-release-plan": "^4.0.16", + "@changesets/git": "^3.0.4", + "@changesets/logger": "^0.1.1", + "@changesets/pre": "^2.0.2", + "@changesets/read": "^0.6.7", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@changesets/write": "^0.4.0", + "@inquirer/external-editor": "^1.0.2", + "@manypkg/get-packages": "^1.1.3", + "ansi-colors": "^4.1.3", + "enquirer": "^2.4.1", + "fs-extra": "^7.0.1", + "mri": "^1.2.0", + "package-manager-detector": "^0.2.0", + "picocolors": "^1.1.0", + "resolve-from": "^5.0.0", + "semver": "^7.5.3", + "spawndamnit": "^3.0.1", + "term-size": "^2.1.0" }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" + "bin": { + "changeset": "bin.js" } }, - "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], + "node_modules/@changesets/cli/node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "dev": true, "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, "engines": { "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], + "node_modules/@changesets/cli/node_modules/fs-extra": { + "version": "7.0.1", + "dev": true, "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "node": ">=6 <7 || >=8" } }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], + "node_modules/@changesets/cli/node_modules/jsonfile": { + "version": "4.0.0", + "dev": true, "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], + "node_modules/@changesets/cli/node_modules/package-manager-detector": { + "version": "0.2.11", + "dev": true, + "license": "MIT", + "dependencies": { + "quansync": "^0.2.7" + } + }, + "node_modules/@changesets/cli/node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/@csstools/media-query-list-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", - "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], + "node_modules/@changesets/cli/node_modules/universalify": { + "version": "0.1.2", + "dev": true, "license": "MIT", "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "node": ">= 4.0.0" } }, - "node_modules/@csstools/postcss-alpha-function": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.1.tgz", - "integrity": "sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "node_modules/@changesets/config": { + "version": "3.1.4", + "dev": true, + "license": "MIT", "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.4", + "@changesets/logger": "^0.1.1", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1", + "micromatch": "^4.0.8" } }, - "node_modules/@csstools/postcss-cascade-layers": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz", - "integrity": "sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "node_modules/@changesets/config/node_modules/fs-extra": { + "version": "7.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=6 <7 || >=8" } }, - "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "node_modules/@changesets/config/node_modules/jsonfile": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@changesets/config/node_modules/universalify": { + "version": "0.1.2", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" + "node": ">= 4.0.0" } }, - "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "node_modules/@changesets/errors": { + "version": "0.2.0", + "dev": true, "license": "MIT", "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" + "extendable-error": "^0.1.5" } }, - "node_modules/@csstools/postcss-color-function": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz", - "integrity": "sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "node_modules/@changesets/get-dependents-graph": { + "version": "2.1.4", + "dev": true, + "license": "MIT", "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "picocolors": "^1.1.0", + "semver": "^7.5.3" } }, - "node_modules/@csstools/postcss-color-function-display-p3-linear": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.1.tgz", - "integrity": "sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "node_modules/@changesets/get-github-info": { + "version": "0.6.0", + "dev": true, + "license": "MIT", "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "dataloader": "^1.4.0", + "node-fetch": "^2.5.0" } }, - "node_modules/@csstools/postcss-color-mix-function": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.12.tgz", - "integrity": "sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "node_modules/@changesets/get-github-info/node_modules/dataloader": { + "version": "1.4.0", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@changesets/get-release-plan": { + "version": "4.0.16", + "dev": true, + "license": "MIT", "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "@changesets/assemble-release-plan": "^6.0.10", + "@changesets/config": "^3.1.4", + "@changesets/pre": "^2.0.2", + "@changesets/read": "^0.6.7", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3" } }, - "node_modules/@csstools/postcss-color-mix-variadic-function-arguments": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.2.tgz", - "integrity": "sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "node_modules/@changesets/get-version-range-type": { + "version": "0.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@changesets/git": { + "version": "3.0.4", + "dev": true, + "license": "MIT", "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "@changesets/errors": "^0.2.0", + "@manypkg/get-packages": "^1.1.3", + "is-subdir": "^1.1.1", + "micromatch": "^4.0.8", + "spawndamnit": "^3.0.1" } }, - "node_modules/@csstools/postcss-content-alt-text": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.8.tgz", - "integrity": "sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q==", + "node_modules/@changesets/logger": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.0" + } + }, + "node_modules/@changesets/parse": { + "version": "0.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "js-yaml": "^4.1.1" + } + }, + "node_modules/@changesets/pre": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1" + } + }, + "node_modules/@changesets/pre/node_modules/fs-extra": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@changesets/pre/node_modules/jsonfile": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@changesets/pre/node_modules/universalify": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@changesets/read": { + "version": "0.6.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/git": "^3.0.4", + "@changesets/logger": "^0.1.1", + "@changesets/parse": "^0.4.3", + "@changesets/types": "^6.1.0", + "fs-extra": "^7.0.1", + "p-filter": "^2.1.0", + "picocolors": "^1.1.0" + } + }, + "node_modules/@changesets/read/node_modules/fs-extra": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@changesets/read/node_modules/jsonfile": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@changesets/read/node_modules/universalify": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@changesets/should-skip-package": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3" + } + }, + "node_modules/@changesets/types": { + "version": "6.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@changesets/write": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "fs-extra": "^7.0.1", + "human-id": "^4.1.1", + "prettier": "^2.7.1" + } + }, + "node_modules/@changesets/write/node_modules/fs-extra": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@changesets/write/node_modules/jsonfile": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@changesets/write/node_modules/universalify": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "license": "Apache-2.0" + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@coral-xyz/anchor": { + "version": "0.29.0", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "@coral-xyz/borsh": "^0.29.0", + "@noble/hashes": "^1.3.1", + "@solana/web3.js": "^1.68.0", + "bn.js": "^5.1.2", + "bs58": "^4.0.1", + "buffer-layout": "^1.2.2", + "camelcase": "^6.3.0", + "cross-fetch": "^3.1.5", + "crypto-hash": "^1.3.0", + "eventemitter3": "^4.0.7", + "pako": "^2.0.3", + "snake-case": "^3.0.4", + "superstruct": "^0.15.4", + "toml": "^3.0.0" + }, + "engines": { + "node": ">=11" + } + }, + "node_modules/@coral-xyz/anchor/node_modules/@noble/hashes": { + "version": "1.8.0", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@coral-xyz/anchor/node_modules/base-x": { + "version": "3.0.11", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@coral-xyz/anchor/node_modules/bs58": { + "version": "4.0.1", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/@coral-xyz/anchor/node_modules/eventemitter3": { + "version": "4.0.7", + "license": "MIT" + }, + "node_modules/@coral-xyz/borsh": { + "version": "0.29.0", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.1.2", + "buffer-layout": "^1.2.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@solana/web3.js": "^1.68.0" + } + }, + "node_modules/@csstools/cascade-layer-name-parser": { + "version": "2.0.5", "funding": [ { "type": "github", @@ -2969,24 +2877,17 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, + "license": "MIT", "engines": { "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@csstools/postcss-contrast-color-function": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-contrast-color-function/-/postcss-contrast-color-function-2.0.12.tgz", - "integrity": "sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA==", + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", "funding": [ { "type": "github", @@ -2998,24 +2899,12 @@ } ], "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-exponential-functions": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz", - "integrity": "sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw==", + "node_modules/@csstools/css-calc": { + "version": "2.1.4", "funding": [ { "type": "github", @@ -3026,23 +2915,17 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, + "license": "MIT", "engines": { "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@csstools/postcss-font-format-keywords": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", - "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", "funding": [ { "type": "github", @@ -3053,22 +2936,21 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT-0", + "license": "MIT", "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" }, "engines": { "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@csstools/postcss-gamut-mapping": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz", - "integrity": "sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw==", + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", "funding": [ { "type": "github", @@ -3079,23 +2961,17 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, + "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4" + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.12.tgz", - "integrity": "sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow==", + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", "funding": [ { "type": "github", @@ -3106,25 +2982,14 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, + "license": "MIT", + "peer": true, "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-hwb-function": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.12.tgz", - "integrity": "sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA==", + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.3", "funding": [ { "type": "github", @@ -3135,25 +3000,17 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, + "license": "MIT", "engines": { "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@csstools/postcss-ic-unit": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.4.tgz", - "integrity": "sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg==", + "node_modules/@csstools/postcss-alpha-function": { + "version": "1.0.1", "funding": [ { "type": "github", @@ -3166,32 +3023,12 @@ ], "license": "MIT-0", "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" + "@csstools/utilities": "^2.0.0" }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-initial": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz", - "integrity": "sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", "engines": { "node": ">=18" }, @@ -3199,10 +3036,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz", - "integrity": "sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ==", + "node_modules/@csstools/postcss-cascade-layers": { + "version": "5.0.2", "funding": [ { "type": "github", @@ -3225,10 +3060,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { + "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", "funding": [ { "type": "github", @@ -3247,11 +3080,10 @@ "postcss-selector-parser": "^7.0.0" } }, - "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { + "version": "7.1.5", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -3260,10 +3092,8 @@ "node": ">=4" } }, - "node_modules/@csstools/postcss-light-dark-function": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz", - "integrity": "sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA==", + "node_modules/@csstools/postcss-color-function": { + "version": "4.0.12", "funding": [ { "type": "github", @@ -3276,6 +3106,7 @@ ], "license": "MIT-0", "dependencies": { + "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", @@ -3288,10 +3119,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-logical-float-and-clear": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", - "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", + "node_modules/@csstools/postcss-color-function-display-p3-linear": { + "version": "1.0.1", "funding": [ { "type": "github", @@ -3303,6 +3132,13 @@ } ], "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, "engines": { "node": ">=18" }, @@ -3310,10 +3146,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-logical-overflow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", - "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", + "node_modules/@csstools/postcss-color-mix-function": { + "version": "3.0.12", "funding": [ { "type": "github", @@ -3325,6 +3159,13 @@ } ], "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, "engines": { "node": ">=18" }, @@ -3332,10 +3173,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-logical-overscroll-behavior": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", - "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", + "node_modules/@csstools/postcss-color-mix-variadic-function-arguments": { + "version": "1.0.2", "funding": [ { "type": "github", @@ -3347,6 +3186,13 @@ } ], "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, "engines": { "node": ">=18" }, @@ -3354,10 +3200,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-logical-resize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", - "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", + "node_modules/@csstools/postcss-content-alt-text": { + "version": "2.0.8", "funding": [ { "type": "github", @@ -3370,7 +3214,10 @@ ], "license": "MIT-0", "dependencies": { - "postcss-value-parser": "^4.2.0" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" }, "engines": { "node": ">=18" @@ -3379,10 +3226,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-logical-viewport-units": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz", - "integrity": "sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ==", + "node_modules/@csstools/postcss-contrast-color-function": { + "version": "2.0.12", "funding": [ { "type": "github", @@ -3395,7 +3240,10 @@ ], "license": "MIT-0", "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -3405,10 +3253,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-media-minmax": { + "node_modules/@csstools/postcss-exponential-functions": { "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz", - "integrity": "sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig==", "funding": [ { "type": "github", @@ -3419,12 +3265,11 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT", + "license": "MIT-0", "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3" + "@csstools/css-tokenizer": "^3.0.4" }, "engines": { "node": ">=18" @@ -3433,10 +3278,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz", - "integrity": "sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg==", + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "4.0.0", "funding": [ { "type": "github", @@ -3449,9 +3292,8 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3" + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" }, "engines": { "node": ">=18" @@ -3460,10 +3302,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-nested-calc": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", - "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", + "node_modules/@csstools/postcss-gamut-mapping": { + "version": "2.0.11", "funding": [ { "type": "github", @@ -3476,8 +3316,9 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" }, "engines": { "node": ">=18" @@ -3486,10 +3327,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-normalize-display-values": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.1.tgz", - "integrity": "sha512-TQUGBuRvxdc7TgNSTevYqrL8oItxiwPDixk20qCB5me/W8uF7BPbhRrAvFuhEoywQp/woRsUZ6SJ+sU5idZAIA==", + "node_modules/@csstools/postcss-gradients-interpolation-method": { + "version": "5.0.12", "funding": [ { "type": "github", @@ -3502,7 +3341,11 @@ ], "license": "MIT-0", "dependencies": { - "postcss-value-parser": "^4.2.0" + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" }, "engines": { "node": ">=18" @@ -3511,10 +3354,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-oklab-function": { + "node_modules/@csstools/postcss-hwb-function": { "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.12.tgz", - "integrity": "sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg==", "funding": [ { "type": "github", @@ -3540,10 +3381,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-position-area-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-position-area-property/-/postcss-position-area-property-1.0.0.tgz", - "integrity": "sha512-fUP6KR8qV2NuUZV3Cw8itx0Ep90aRjAZxAEzC3vrl6yjFv+pFsQbR18UuQctEKmA72K9O27CoYiKEgXxkqjg8Q==", + "node_modules/@csstools/postcss-ic-unit": { + "version": "4.0.4", "funding": [ { "type": "github", @@ -3555,6 +3394,11 @@ } ], "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, "engines": { "node": ">=18" }, @@ -3562,10 +3406,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.1.tgz", - "integrity": "sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw==", + "node_modules/@csstools/postcss-initial": { + "version": "2.0.1", "funding": [ { "type": "github", @@ -3577,9 +3419,6 @@ } ], "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, "engines": { "node": ">=18" }, @@ -3587,10 +3426,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-property-rule-prelude-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-property-rule-prelude-list/-/postcss-property-rule-prelude-list-1.0.0.tgz", - "integrity": "sha512-IxuQjUXq19fobgmSSvUDO7fVwijDJaZMvWQugxfEUxmjBeDCVaDuMpsZ31MsTm5xbnhA+ElDi0+rQ7sQQGisFA==", + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "5.0.3", "funding": [ { "type": "github", @@ -3603,8 +3440,8 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" }, "engines": { "node": ">=18" @@ -3613,10 +3450,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-random-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz", - "integrity": "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==", + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", "funding": [ { "type": "github", @@ -3628,22 +3463,27 @@ } ], "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, "engines": { "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4" + "postcss-selector-parser": "^7.0.0" } }, - "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.12.tgz", - "integrity": "sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw==", + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.5", + "license": "MIT", + "peer": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-light-dark-function": { + "version": "2.0.11", "funding": [ { "type": "github", @@ -3656,7 +3496,6 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "@csstools/postcss-progressive-custom-properties": "^4.2.1", @@ -3669,10 +3508,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-scope-pseudo-class": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", - "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", + "node_modules/@csstools/postcss-logical-float-and-clear": { + "version": "3.0.0", "funding": [ { "type": "github", @@ -3684,9 +3521,6 @@ } ], "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, "engines": { "node": ">=18" }, @@ -3694,23 +3528,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@csstools/postcss-sign-functions": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz", - "integrity": "sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg==", + "node_modules/@csstools/postcss-logical-overflow": { + "version": "2.0.0", "funding": [ { "type": "github", @@ -3722,11 +3541,6 @@ } ], "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, "engines": { "node": ">=18" }, @@ -3734,10 +3548,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz", - "integrity": "sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA==", + "node_modules/@csstools/postcss-logical-overscroll-behavior": { + "version": "2.0.0", "funding": [ { "type": "github", @@ -3749,11 +3561,6 @@ } ], "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, "engines": { "node": ">=18" }, @@ -3761,10 +3568,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-syntax-descriptor-syntax-production": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-syntax-descriptor-syntax-production/-/postcss-syntax-descriptor-syntax-production-1.0.1.tgz", - "integrity": "sha512-GneqQWefjM//f4hJ/Kbox0C6f2T7+pi4/fqTqOFGTL3EjnvOReTqO1qUQ30CaUjkwjYq9qZ41hzarrAxCc4gow==", + "node_modules/@csstools/postcss-logical-resize": { + "version": "3.0.0", "funding": [ { "type": "github", @@ -3777,7 +3582,7 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "postcss-value-parser": "^4.2.0" }, "engines": { "node": ">=18" @@ -3786,10 +3591,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-system-ui-font-family": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-system-ui-font-family/-/postcss-system-ui-font-family-1.0.0.tgz", - "integrity": "sha512-s3xdBvfWYfoPSBsikDXbuorcMG1nN1M6GdU0qBsGfcmNR0A/qhloQZpTxjA3Xsyrk1VJvwb2pOfiOT3at/DuIQ==", + "node_modules/@csstools/postcss-logical-viewport-units": { + "version": "3.0.4", "funding": [ { "type": "github", @@ -3802,8 +3605,8 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/utilities": "^2.0.0" }, "engines": { "node": ">=18" @@ -3812,10 +3615,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz", - "integrity": "sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA==", + "node_modules/@csstools/postcss-media-minmax": { + "version": "2.0.9", "funding": [ { "type": "github", @@ -3826,10 +3627,12 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT-0", + "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "postcss-value-parser": "^4.2.0" + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" }, "engines": { "node": ">=18" @@ -3838,10 +3641,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz", - "integrity": "sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A==", + "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { + "version": "3.0.5", "funding": [ { "type": "github", @@ -3854,9 +3655,9 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-calc": "^2.1.4", "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" }, "engines": { "node": ">=18" @@ -3865,10 +3666,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-unset-value": { + "node_modules/@csstools/postcss-nested-calc": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", - "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", "funding": [ { "type": "github", @@ -3880,6 +3679,10 @@ } ], "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, "engines": { "node": ">=18" }, @@ -3887,10 +3690,8 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/utilities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", - "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "4.0.1", "funding": [ { "type": "github", @@ -3902,6 +3703,9 @@ } ], "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, "engines": { "node": ">=18" }, @@ -3909,826 +3713,932 @@ "postcss": "^8.4" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "license": "MIT", + "node_modules/@csstools/postcss-oklab-function": { + "version": "4.0.12", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, "engines": { - "node": ">=10.0.0" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@docsearch/core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@docsearch/core/-/core-4.7.0.tgz", - "integrity": "sha512-p/9xVKmPDj3FPvMfPf5naVO3Ej8SCbcUugGvx1+8GgkuBNbqxqN2Irx3WLBv8VY0jH7XpRwKWdlmjXLZsmTLsg==", - "license": "MIT", - "peerDependencies": { - "@types/react": ">= 16.8.0 < 20.0.0", - "react": ">= 16.8.0 < 20.0.0", - "react-dom": ">= 16.8.0 < 20.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true + "node_modules/@csstools/postcss-position-area-property": { + "version": "1.0.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" }, - "react-dom": { - "optional": true + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@docsearch/css": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.7.0.tgz", - "integrity": "sha512-Sk5xkdRFeE7PeWjG9l4AfTwdvMfr9wHiwNNCpHXT4v4SNyNMKdHGvEILc31BgaVFGDDNbv5u/a73tofRiwbEZw==", - "license": "MIT" - }, - "node_modules/@docsearch/react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-4.7.0.tgz", - "integrity": "sha512-x6oedjJ8O8/pIDBsMo5Orca3/6cQCz616/CwthVe68l43mqnj2lrJ9kFQITBqy8hMsS3nWeBWFoVO5dJ1DCFKA==", - "license": "MIT", + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "4.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@algolia/autocomplete-core": "1.19.2", - "@docsearch/core": "4.7.0", - "@docsearch/css": "4.7.0" + "postcss-value-parser": "^4.2.0" }, - "peerDependencies": { - "@types/react": ">= 16.8.0 < 20.0.0", - "react": ">= 16.8.0 < 20.0.0", - "react-dom": ">= 16.8.0 < 20.0.0", - "search-insights": ">= 1 < 3" + "engines": { + "node": ">=18" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-property-rule-prelude-list": { + "version": "1.0.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" }, - "search-insights": { - "optional": true + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-core": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz", - "integrity": "sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==", - "license": "MIT", + "node_modules/@csstools/postcss-random-function": { + "version": "2.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.19.2", - "@algolia/autocomplete-shared": "1.19.2" + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz", - "integrity": "sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==", - "license": "MIT", + "node_modules/@csstools/postcss-relative-color-syntax": { + "version": "3.0.12", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@algolia/autocomplete-shared": "1.19.2" + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" }, "peerDependencies": { - "search-insights": ">= 1 < 3" + "postcss": "^8.4" } }, - "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-shared": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz", - "integrity": "sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==", - "license": "MIT", + "node_modules/@csstools/postcss-scope-pseudo-class": { + "version": "4.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" + "postcss": "^8.4" } }, - "node_modules/@docusaurus/babel": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.10.2.tgz", - "integrity": "sha512-aJ1hpGyvfkte3dDAfNbWM4biW4yWZBVz7TIGLZP+v+tWOBgxX3e0N5ZIXHIvmfNNXTI77pcHUx3KmtOk05Ze3Q==", + "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.5", "license": "MIT", "dependencies": { - "@babel/core": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.25.9", - "@babel/preset-env": "^7.25.9", - "@babel/preset-react": "^7.25.9", - "@babel/preset-typescript": "^7.25.9", - "@babel/runtime": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@docusaurus/logger": "3.10.2", - "@docusaurus/utils": "3.10.2", - "babel-plugin-dynamic-import-node": "^2.3.3", - "fs-extra": "^11.1.1", - "tslib": "^2.6.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">=20.0" + "node": ">=4" } }, - "node_modules/@docusaurus/bundler": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.10.2.tgz", - "integrity": "sha512-i0ZNcy0f0WhaOlYVgzLsWhIoEXO9kS3HRoKPtgE6vQtZUq7arKZaYdNBudr3mqCmd+TyOkwtwfHgs1ENj07r5g==", - "license": "MIT", + "node_modules/@csstools/postcss-sign-functions": { + "version": "1.1.4", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@babel/core": "^7.25.9", - "@docusaurus/babel": "3.10.2", - "@docusaurus/cssnano-preset": "3.10.2", - "@docusaurus/logger": "3.10.2", - "@docusaurus/types": "3.10.2", - "@docusaurus/utils": "3.10.2", - "babel-loader": "^9.2.1", - "clean-css": "^5.3.3", - "copy-webpack-plugin": "^11.0.0", - "css-loader": "^6.11.0", - "css-minimizer-webpack-plugin": "^5.0.1", - "cssnano": "^6.1.2", - "file-loader": "^6.2.0", - "html-minifier-terser": "^7.2.0", - "mini-css-extract-plugin": "^2.9.2", - "null-loader": "^4.0.1", - "postcss": "^8.5.4", - "postcss-loader": "^7.3.4", - "postcss-preset-env": "^10.2.1", - "terser-webpack-plugin": "^5.3.9", - "tslib": "^2.6.0", - "url-loader": "^4.1.1", - "webpack": "^5.95.0", - "webpackbar": "^7.0.0" + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" }, "engines": { - "node": ">=20.0" + "node": ">=18" }, "peerDependencies": { - "@docusaurus/faster": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/faster": { - "optional": true - } + "postcss": "^8.4" } }, - "node_modules/@docusaurus/core": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.10.2.tgz", - "integrity": "sha512-EYByj6nk+aD9KeVxV6Hmo2/nAAT79P21Y82ycTBOBtrmqilloIbIEhgL2/8Xpt2Jz/pgNqHAwyusOGwmbKeJmA==", - "license": "MIT", + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "4.0.9", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@docusaurus/babel": "3.10.2", - "@docusaurus/bundler": "3.10.2", - "@docusaurus/logger": "3.10.2", - "@docusaurus/mdx-loader": "3.10.2", - "@docusaurus/utils": "3.10.2", - "@docusaurus/utils-common": "3.10.2", - "@docusaurus/utils-validation": "3.10.2", - "boxen": "^6.2.1", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "cli-table3": "^0.6.3", - "combine-promises": "^1.1.0", - "commander": "^5.1.0", - "core-js": "^3.31.1", - "detect-port": "^2.1.0", - "escape-html": "^1.0.3", - "eta": "^2.2.0", - "eval": "^0.1.8", - "execa": "^5.1.1", - "fs-extra": "^11.1.1", - "html-tags": "^3.3.1", - "html-webpack-plugin": "^5.6.0", - "leven": "^3.1.0", - "lodash": "^4.17.21", - "open": "^8.4.0", - "p-map": "^4.0.0", - "prompts": "^2.4.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", - "react-loadable-ssr-addon-v5-slorber": "^1.0.3", - "react-router": "^5.3.4", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.3.4", - "semver": "^7.5.4", - "serve-handler": "^6.1.7", - "tinypool": "^1.0.2", - "tslib": "^2.6.0", - "update-notifier": "^6.0.2", - "webpack": "^5.95.0", - "webpack-bundle-analyzer": "^4.10.2", - "webpack-dev-server": "^5.2.2", - "webpack-merge": "^6.0.1" - }, - "bin": { - "docusaurus": "bin/docusaurus.mjs" + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" }, "engines": { - "node": ">=20.0" + "node": ">=18" }, "peerDependencies": { - "@docusaurus/faster": "*", - "@mdx-js/react": "^3.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@docusaurus/faster": { - "optional": true - } + "postcss": "^8.4" } }, - "node_modules/@docusaurus/core/node_modules/@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", - "license": "MIT", + "node_modules/@csstools/postcss-syntax-descriptor-syntax-production": { + "version": "1.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-tokenizer": "^3.0.4" + }, "engines": { - "node": ">=14.16" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@docusaurus/core/node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "license": "MIT", + "node_modules/@csstools/postcss-system-ui-font-family": { + "version": "1.0.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "defer-to-connect": "^2.0.1" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" }, "engines": { - "node": ">=14.16" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@docusaurus/core/node_modules/cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", - "license": "MIT", + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "4.0.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" + "@csstools/color-helpers": "^5.1.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=14.16" - } - }, - "node_modules/@docusaurus/core/node_modules/camelcase": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", - "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", - "license": "MIT", - "engines": { - "node": ">=14.16" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@docusaurus/core/node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "license": "MIT", - "engines": { - "node": ">= 6" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@docusaurus/core/node_modules/configstore": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", - "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", - "license": "BSD-2-Clause", + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "4.0.9", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "dot-prop": "^6.0.1", - "graceful-fs": "^4.2.6", - "unique-string": "^3.0.0", - "write-file-atomic": "^3.0.3", - "xdg-basedir": "^5.0.1" + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" }, "engines": { - "node": ">=12" + "node": ">=18" }, - "funding": { - "url": "https://github.com/yeoman/configstore?sponsor=1" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@docusaurus/core/node_modules/dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", - "license": "MIT", - "dependencies": { - "is-obj": "^2.0.0" - }, + "node_modules/@csstools/postcss-unset-value": { + "version": "4.0.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": ">=10" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@docusaurus/core/node_modules/got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - }, + "node_modules/@csstools/utilities": { + "version": "2.0.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": ">=14.16" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@docusaurus/core/node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", "license": "MIT", - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10.0.0" } }, - "node_modules/@docusaurus/core/node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "node_modules/@docsearch/core": { + "version": "4.7.0", "license": "MIT", - "engines": { - "node": ">=8" + "peerDependencies": { + "@types/react": ">= 16.8.0 < 20.0.0", + "react": ">= 16.8.0 < 20.0.0", + "react-dom": ">= 16.8.0 < 20.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } }, - "node_modules/@docusaurus/core/node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } + "node_modules/@docsearch/css": { + "version": "4.7.0", + "license": "MIT" }, - "node_modules/@docusaurus/core/node_modules/latest-version": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", - "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "node_modules/@docsearch/react": { + "version": "4.7.0", "license": "MIT", "dependencies": { - "package-json": "^8.1.0" + "@algolia/autocomplete-core": "1.19.2", + "@docsearch/core": "4.7.0", + "@docsearch/css": "4.7.0" }, - "engines": { - "node": ">=14.16" + "peerDependencies": { + "@types/react": ">= 16.8.0 < 20.0.0", + "react": ">= 16.8.0 < 20.0.0", + "react-dom": ">= 16.8.0 < 20.0.0", + "search-insights": ">= 1 < 3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } } }, - "node_modules/@docusaurus/core/node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-core": { + "version": "1.19.2", "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.19.2", + "@algolia/autocomplete-shared": "1.19.2" + } + }, + "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.19.2", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.19.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "search-insights": ">= 1 < 3" } }, - "node_modules/@docusaurus/core/node_modules/p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-shared": { + "version": "1.19.2", "license": "MIT", - "engines": { - "node": ">=12.20" + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" } }, - "node_modules/@docusaurus/core/node_modules/package-json": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", - "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", + "node_modules/@docusaurus/babel": { + "version": "3.10.2", "license": "MIT", "dependencies": { - "got": "^12.1.0", - "registry-auth-token": "^5.0.1", - "registry-url": "^6.0.0", - "semver": "^7.3.7" + "@babel/core": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.25.9", + "@babel/preset-env": "^7.25.9", + "@babel/preset-react": "^7.25.9", + "@babel/preset-typescript": "^7.25.9", + "@babel/runtime": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@docusaurus/logger": "3.10.2", + "@docusaurus/utils": "3.10.2", + "babel-plugin-dynamic-import-node": "^2.3.3", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=20.0" } }, - "node_modules/@docusaurus/core/node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "node_modules/@docusaurus/bundler": { + "version": "3.10.2", "license": "MIT", "dependencies": { - "lowercase-keys": "^3.0.0" + "@babel/core": "^7.25.9", + "@docusaurus/babel": "3.10.2", + "@docusaurus/cssnano-preset": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "babel-loader": "^9.2.1", + "clean-css": "^5.3.3", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.11.0", + "css-minimizer-webpack-plugin": "^5.0.1", + "cssnano": "^6.1.2", + "file-loader": "^6.2.0", + "html-minifier-terser": "^7.2.0", + "mini-css-extract-plugin": "^2.9.2", + "null-loader": "^4.0.1", + "postcss": "^8.5.4", + "postcss-loader": "^7.3.4", + "postcss-preset-env": "^10.2.1", + "terser-webpack-plugin": "^5.3.9", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "webpack": "^5.95.0", + "webpackbar": "^7.0.0" }, "engines": { - "node": ">=14.16" + "node": ">=20.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@docusaurus/core/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" + "peerDependencies": { + "@docusaurus/faster": "*" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } } }, - "node_modules/@docusaurus/core/node_modules/update-notifier": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", - "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", - "license": "BSD-2-Clause", + "node_modules/@docusaurus/core": { + "version": "3.10.2", + "license": "MIT", "dependencies": { - "boxen": "^7.0.0", - "chalk": "^5.0.1", - "configstore": "^6.0.0", - "has-yarn": "^3.0.0", - "import-lazy": "^4.0.0", - "is-ci": "^3.0.1", - "is-installed-globally": "^0.4.0", - "is-npm": "^6.0.0", - "is-yarn-global": "^0.4.0", - "latest-version": "^7.0.0", - "pupa": "^3.1.0", - "semver": "^7.3.7", - "semver-diff": "^4.0.0", - "xdg-basedir": "^5.1.0" + "@docusaurus/babel": "3.10.2", + "@docusaurus/bundler": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "boxen": "^6.2.1", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cli-table3": "^0.6.3", + "combine-promises": "^1.1.0", + "commander": "^5.1.0", + "core-js": "^3.31.1", + "detect-port": "^2.1.0", + "escape-html": "^1.0.3", + "eta": "^2.2.0", + "eval": "^0.1.8", + "execa": "^5.1.1", + "fs-extra": "^11.1.1", + "html-tags": "^3.3.1", + "html-webpack-plugin": "^5.6.0", + "leven": "^3.1.0", + "lodash": "^4.17.21", + "open": "^8.4.0", + "p-map": "^4.0.0", + "prompts": "^2.4.2", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", + "react-loadable-ssr-addon-v5-slorber": "^1.0.3", + "react-router": "^5.3.4", + "react-router-config": "^5.1.1", + "react-router-dom": "^5.3.4", + "semver": "^7.5.4", + "serve-handler": "^6.1.7", + "tinypool": "^1.0.2", + "tslib": "^2.6.0", + "update-notifier": "^6.0.2", + "webpack": "^5.95.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-dev-server": "^5.2.2", + "webpack-merge": "^6.0.1" + }, + "bin": { + "docusaurus": "bin/docusaurus.mjs" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/faster": "*", + "@mdx-js/react": "^3.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" }, + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } + } + }, + "node_modules/@docusaurus/core/node_modules/@sindresorhus/is": { + "version": "5.6.0", + "license": "MIT", "engines": { "node": ">=14.16" }, "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@docusaurus/core/node_modules/update-notifier/node_modules/boxen": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", - "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", + "node_modules/@docusaurus/core/node_modules/@szmarczak/http-timer": { + "version": "5.0.1", "license": "MIT", "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^7.0.1", - "chalk": "^5.2.0", - "cli-boxes": "^3.0.0", - "string-width": "^5.1.2", - "type-fest": "^2.13.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.1.0" + "defer-to-connect": "^2.0.1" }, "engines": { "node": ">=14.16" + } + }, + "node_modules/@docusaurus/core/node_modules/cacheable-request": { + "version": "10.2.14", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=14.16" } }, - "node_modules/@docusaurus/core/node_modules/update-notifier/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "node_modules/@docusaurus/core/node_modules/camelcase": { + "version": "7.0.1", "license": "MIT", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@docusaurus/cssnano-preset": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.10.2.tgz", - "integrity": "sha512-4gCnHRbJLTloiwfvFAa92tgb2gI4KYhvjfQVYnEaiMO/EgvWfCo1LwytHXen+1oZAN0VAlS0JAPxp3MsvKDa3A==", + "node_modules/@docusaurus/core/node_modules/commander": { + "version": "5.1.0", "license": "MIT", - "dependencies": { - "cssnano-preset-advanced": "^6.1.2", - "postcss": "^8.5.4", - "postcss-sort-media-queries": "^5.2.0", - "tslib": "^2.6.0" - }, "engines": { - "node": ">=20.0" + "node": ">= 6" } }, - "node_modules/@docusaurus/logger": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.10.2.tgz", - "integrity": "sha512-gSEwqtPfCAnC3ZSJY6xL7tcIfgg0vFD39jbv93eakuweyvO2864xR0K+kmKwBhkTCtWRNjuGGnb5rdmkD/ndqw==", - "license": "MIT", + "node_modules/@docusaurus/core/node_modules/configstore": { + "version": "6.0.0", + "license": "BSD-2-Clause", "dependencies": { - "chalk": "^4.1.2", - "tslib": "^2.6.0" + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" }, "engines": { - "node": ">=20.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/yeoman/configstore?sponsor=1" } }, - "node_modules/@docusaurus/mdx-loader": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.10.2.tgz", - "integrity": "sha512-9Fd4V/SFjfrVQ0JH5EN0+iPWyFunvTeQE3gfyFeetqPaXMP0OylIjOw16dCuXG4NZJrYdBqwzjh18/h3gRi47w==", + "node_modules/@docusaurus/core/node_modules/dot-prop": { + "version": "6.0.1", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.10.2", - "@docusaurus/utils": "3.10.2", - "@docusaurus/utils-validation": "3.10.2", - "@mdx-js/mdx": "^3.0.0", - "@slorber/remark-comment": "^1.0.0", - "escape-html": "^1.0.3", - "estree-util-value-to-estree": "^3.0.1", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "image-size": "^2.0.2", - "mdast-util-mdx": "^3.0.0", - "mdast-util-to-string": "^4.0.0", - "rehype-raw": "^7.0.0", - "remark-directive": "^3.0.0", - "remark-emoji": "^4.0.0", - "remark-frontmatter": "^5.0.0", - "remark-gfm": "^4.0.0", - "stringify-object": "^3.3.0", - "tslib": "^2.6.0", - "unified": "^11.0.3", - "unist-util-visit": "^5.0.0", - "url-loader": "^4.1.1", - "vfile": "^6.0.1", - "webpack": "^5.88.1" + "is-obj": "^2.0.0" }, "engines": { - "node": ">=20.0" + "node": ">=10" }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@docusaurus/module-type-aliases": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.10.2.tgz", - "integrity": "sha512-h/I5e4jaAhDHW4vaLENi1i2hnOEnXY1t9R+nnRTbgUl7ymVRzN/HF7dDfj8rKYGj8gfIge+Ef+iYRAMtbGvsrQ==", + "node_modules/@docusaurus/core/node_modules/emoji-regex": { + "version": "9.2.2", + "license": "MIT" + }, + "node_modules/@docusaurus/core/node_modules/got": { + "version": "12.6.1", "license": "MIT", "dependencies": { - "@docusaurus/types": "3.10.2", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "@types/react-router-dom": "*", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" }, - "peerDependencies": { - "react": "*", - "react-dom": "*" + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" } }, - "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.10.2.tgz", - "integrity": "sha512-0cbEnNKf0InmLkhj/+nVRmqEnWEoOE8Mh+2x1qOXI0qYpCnphq4RXknVJ8BvybKRXqYVvbmdMfiJSup+k4tm5w==", + "node_modules/@docusaurus/core/node_modules/is-installed-globally": { + "version": "0.4.0", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.2", - "@docusaurus/logger": "3.10.2", - "@docusaurus/mdx-loader": "3.10.2", - "@docusaurus/theme-common": "3.10.2", - "@docusaurus/types": "3.10.2", - "@docusaurus/utils": "3.10.2", - "@docusaurus/utils-common": "3.10.2", - "@docusaurus/utils-validation": "3.10.2", - "cheerio": "1.0.0-rc.12", - "combine-promises": "^1.1.0", - "feed": "^4.2.2", - "fs-extra": "^11.1.1", - "lodash": "^4.17.21", - "schema-dts": "^1.1.2", - "srcset": "^4.0.0", - "tslib": "^2.6.0", - "unist-util-visit": "^5.0.0", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" }, "engines": { - "node": ">=20.0" + "node": ">=10" }, - "peerDependencies": { - "@docusaurus/plugin-content-docs": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.10.2.tgz", - "integrity": "sha512-Sqwl4FPoZBDrlY8I2VU2H8O0M91CHp9T8ToMSkTZmjvHCif+1laqfXi6sTk8IfyVS/trN5yNjcWd1bFsGB6W5Q==", + "node_modules/@docusaurus/core/node_modules/is-obj": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@docusaurus/core/node_modules/is-path-inside": { + "version": "3.0.3", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@docusaurus/core/node_modules/latest-version": { + "version": "7.0.0", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.2", - "@docusaurus/logger": "3.10.2", - "@docusaurus/mdx-loader": "3.10.2", - "@docusaurus/module-type-aliases": "3.10.2", - "@docusaurus/theme-common": "3.10.2", - "@docusaurus/types": "3.10.2", - "@docusaurus/utils": "3.10.2", - "@docusaurus/utils-common": "3.10.2", - "@docusaurus/utils-validation": "3.10.2", - "@types/react-router-config": "^5.0.7", - "combine-promises": "^1.1.0", - "fs-extra": "^11.1.1", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "schema-dts": "^1.1.2", - "tslib": "^2.6.0", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" + "package-json": "^8.1.0" }, "engines": { - "node": ">=20.0" + "node": ">=14.16" }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.10.2.tgz", - "integrity": "sha512-h5R12sZ/vV9EPiVjvIl9YFCOwkpwXes7dQMYt3EvP6Pphu4amHxxTqWxf08Fl5DR8h+oZMbWpFTNw5vKEYfvzQ==", + "node_modules/@docusaurus/core/node_modules/lowercase-keys": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@docusaurus/core/node_modules/p-cancelable": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@docusaurus/core/node_modules/package-json": { + "version": "8.1.1", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.2", - "@docusaurus/mdx-loader": "3.10.2", - "@docusaurus/types": "3.10.2", - "@docusaurus/utils": "3.10.2", - "@docusaurus/utils-validation": "3.10.2", - "fs-extra": "^11.1.1", - "tslib": "^2.6.0", - "webpack": "^5.88.1" + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" }, "engines": { - "node": ">=20.0" + "node": ">=14.16" }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@docusaurus/plugin-css-cascade-layers": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.10.2.tgz", - "integrity": "sha512-UkdvQby5OQUKWrw3lLnSTJXQ6VETaUVTuPQX9AABtmFm5h+ifEBx1OQ+LN726Q4byuwBf2ElHkf4qU4hTxdvRg==", + "node_modules/@docusaurus/core/node_modules/responselike": { + "version": "3.0.0", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.2", - "@docusaurus/types": "3.10.2", - "@docusaurus/utils": "3.10.2", - "@docusaurus/utils-validation": "3.10.2", - "tslib": "^2.6.0" + "lowercase-keys": "^3.0.0" }, "engines": { - "node": ">=20.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@docusaurus/plugin-debug": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.10.2.tgz", - "integrity": "sha512-8vbZNOSCpnsT57EY6CgN7sgRVmx3KTYwO8Uvo2pbxOyb8tbqAwtT9SslqaQ41HbA1v1hpn5RP7u5s2KvRwAFpQ==", + "node_modules/@docusaurus/core/node_modules/string-width": { + "version": "5.1.2", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.2", - "@docusaurus/types": "3.10.2", - "@docusaurus/utils": "3.10.2", - "fs-extra": "^11.1.1", - "react-json-view-lite": "^2.3.0", - "tslib": "^2.6.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=20.0" + "node": ">=12" }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.10.2.tgz", - "integrity": "sha512-kMHMBK9j4VAtgd5owwrRLRIi0EjkrpXlX7ePj1+y68XfVZV9I1T4S+koPDm+Hfw2TtnyHvh0uNrDvjz+DjQGVA==", + "node_modules/@docusaurus/core/node_modules/type-fest": { + "version": "2.19.0", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@docusaurus/core/node_modules/update-notifier": { + "version": "6.0.2", + "license": "BSD-2-Clause", + "dependencies": { + "boxen": "^7.0.0", + "chalk": "^5.0.1", + "configstore": "^6.0.0", + "has-yarn": "^3.0.0", + "import-lazy": "^4.0.0", + "is-ci": "^3.0.1", + "is-installed-globally": "^0.4.0", + "is-npm": "^6.0.0", + "is-yarn-global": "^0.4.0", + "latest-version": "^7.0.0", + "pupa": "^3.1.0", + "semver": "^7.3.7", + "semver-diff": "^4.0.0", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/@docusaurus/core/node_modules/update-notifier/node_modules/boxen": { + "version": "7.1.1", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.2", - "@docusaurus/types": "3.10.2", - "@docusaurus/utils-validation": "3.10.2", - "tslib": "^2.6.0" + "ansi-align": "^3.0.1", + "camelcase": "^7.0.1", + "chalk": "^5.2.0", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.1.0" }, "engines": { - "node": ">=20.0" + "node": ">=14.16" }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@docusaurus/plugin-google-gtag": { + "node_modules/@docusaurus/core/node_modules/update-notifier/node_modules/chalk": { + "version": "5.6.2", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@docusaurus/core/node_modules/wrap-ansi": { + "version": "8.1.0", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@docusaurus/cssnano-preset": { "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.10.2.tgz", - "integrity": "sha512-Vt90nNFhtAChRe9+it1hcHFgFvETdSnOkL5Bma+p6E/yU2tAYrvvyk+gv+LJGM2ZUkyKuKXLRsZ2Lb0bO7+Vog==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.2", - "@docusaurus/types": "3.10.2", - "@docusaurus/utils-validation": "3.10.2", + "cssnano-preset-advanced": "^6.1.2", + "postcss": "^8.5.4", + "postcss-sort-media-queries": "^5.2.0", "tslib": "^2.6.0" }, "engines": { "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@docusaurus/plugin-google-tag-manager": { + "node_modules/@docusaurus/logger": { "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.10.2.tgz", - "integrity": "sha512-MLCffCldysi/R0nzJQP7ZWd0xAoGNnSTiVOo6TTR6mKVGFhE+/XArGe67ZcaZv1uytgQXoXs92VJrgVDrz80rQ==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.2", - "@docusaurus/types": "3.10.2", - "@docusaurus/utils-validation": "3.10.2", + "chalk": "^4.1.2", "tslib": "^2.6.0" }, "engines": { "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@docusaurus/plugin-sitemap": { + "node_modules/@docusaurus/mdx-loader": { "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.10.2.tgz", - "integrity": "sha512-PODkwg5XetLML3hU/3xpCKJUZ9cqExLaBnD/Fzzwj2VHogLeqnDisLIujae87zuze7T4mCm2A6KEqZkyiz07EQ==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.2", "@docusaurus/logger": "3.10.2", - "@docusaurus/types": "3.10.2", "@docusaurus/utils": "3.10.2", - "@docusaurus/utils-common": "3.10.2", "@docusaurus/utils-validation": "3.10.2", + "@mdx-js/mdx": "^3.0.0", + "@slorber/remark-comment": "^1.0.0", + "escape-html": "^1.0.3", + "estree-util-value-to-estree": "^3.0.1", + "file-loader": "^6.2.0", "fs-extra": "^11.1.1", - "sitemap": "^7.1.1", - "tslib": "^2.6.0" + "image-size": "^2.0.2", + "mdast-util-mdx": "^3.0.0", + "mdast-util-to-string": "^4.0.0", + "rehype-raw": "^7.0.0", + "remark-directive": "^3.0.0", + "remark-emoji": "^4.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.0", + "stringify-object": "^3.3.0", + "tslib": "^2.6.0", + "unified": "^11.0.3", + "unist-util-visit": "^5.0.0", + "url-loader": "^4.1.1", + "vfile": "^6.0.1", + "webpack": "^5.88.1" }, "engines": { "node": ">=20.0" @@ -4738,38 +4648,243 @@ "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@docusaurus/plugin-svgr": { + "node_modules/@docusaurus/module-type-aliases": { "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.10.2.tgz", - "integrity": "sha512-JgfT3jWM0TJ8Uw0cEcqxHpybngQY1vlBYpuuNO+gEh5iPh5Ar+vxq/u9CFrYsWeXy48BN7Db76Pzp2edNXUQ8A==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.10.2", "@docusaurus/types": "3.10.2", - "@docusaurus/utils": "3.10.2", - "@docusaurus/utils-validation": "3.10.2", - "@svgr/core": "8.1.0", - "@svgr/webpack": "^8.1.0", - "tslib": "^2.6.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=20.0" + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "@types/react-router-dom": "*", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" }, "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "react": "*", + "react-dom": "*" } }, - "node_modules/@docusaurus/preset-classic": { + "node_modules/@docusaurus/plugin-content-blog": { "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.10.2.tgz", - "integrity": "sha512-a4B3VczmDl99zK0EufDQYomdJ186WDingjmDXxhN2PNPS9Ty/Y2M5CLFX1KQMRKqRTLiRDKfutzG5IY1FC/ceg==", "license": "MIT", "dependencies": { "@docusaurus/core": "3.10.2", - "@docusaurus/plugin-content-blog": "3.10.2", - "@docusaurus/plugin-content-docs": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "cheerio": "1.0.0-rc.12", + "combine-promises": "^1.1.0", + "feed": "^4.2.2", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "schema-dts": "^1.1.2", + "srcset": "^4.0.0", + "tslib": "^2.6.0", + "unist-util-visit": "^5.0.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-docs": { + "version": "3.10.2", + "license": "MIT", + "peer": true, + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/module-type-aliases": "3.10.2", + "@docusaurus/theme-common": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "@types/react-router-config": "^5.0.7", + "combine-promises": "^1.1.0", + "fs-extra": "^11.1.1", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "schema-dts": "^1.1.2", + "tslib": "^2.6.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-pages": { + "version": "3.10.2", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/mdx-loader": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-css-cascade-layers": { + "version": "3.10.2", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/plugin-debug": { + "version": "3.10.2", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "fs-extra": "^11.1.1", + "react-json-view-lite": "^2.3.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-analytics": { + "version": "3.10.2", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-gtag": { + "version": "3.10.2", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-tag-manager": { + "version": "3.10.2", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-sitemap": { + "version": "3.10.2", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/logger": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "fs-extra": "^11.1.1", + "sitemap": "^7.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-svgr": { + "version": "3.10.2", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/types": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-validation": "3.10.2", + "@svgr/core": "8.1.0", + "@svgr/webpack": "^8.1.0", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/preset-classic": { + "version": "3.10.2", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.2", + "@docusaurus/plugin-content-blog": "3.10.2", + "@docusaurus/plugin-content-docs": "3.10.2", "@docusaurus/plugin-content-pages": "3.10.2", "@docusaurus/plugin-css-cascade-layers": "3.10.2", "@docusaurus/plugin-debug": "3.10.2", @@ -4793,8 +4908,6 @@ }, "node_modules/@docusaurus/theme-classic": { "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.10.2.tgz", - "integrity": "sha512-JqTSLQmqmA9uKWZsD5iwBGJ4JyKB4/yTw6PsSXVPRJG/6GAm/u+add9Iip+hvwP12/AnPNztrdxsI14NJW4KeA==", "license": "MIT", "dependencies": { "@docusaurus/core": "3.10.2", @@ -4834,9 +4947,8 @@ }, "node_modules/@docusaurus/theme-common": { "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.10.2.tgz", - "integrity": "sha512-R9b/vMpK1yye6hNZTA6x/ivRv+at6GhxnXcxkpzCGzO1R1RwiquqiFg2wMFh6aqlJTpWRFKpFD2TzCDQcyOU0A==", "license": "MIT", + "peer": true, "dependencies": { "@docusaurus/mdx-loader": "3.10.2", "@docusaurus/module-type-aliases": "3.10.2", @@ -4862,8 +4974,6 @@ }, "node_modules/@docusaurus/theme-mermaid": { "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.10.2.tgz", - "integrity": "sha512-Stssh5MYQJ+EdYugUXf+ZcpeJFQPKXf0KCd/SWp10o3CmXNaOoh5IEgVjVqY1e1XhQf3on4+Y4BnrMiD95E2SQ==", "license": "MIT", "dependencies": { "@docusaurus/core": "3.10.2", @@ -4890,8 +5000,6 @@ }, "node_modules/@docusaurus/theme-search-algolia": { "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.10.2.tgz", - "integrity": "sha512-1msxllyhi/5m77JukXtp5UFnUAriwZIC1oJ7MTnpQpCwLTbclJi5BK5n28CTZuSXpQN2ewbbnqRgAhMM6c6ihg==", "license": "MIT", "dependencies": { "@algolia/autocomplete-core": "^1.19.2", @@ -4922,8 +5030,6 @@ }, "node_modules/@docusaurus/theme-translations": { "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.10.2.tgz", - "integrity": "sha512-iv20wrxnyXkY89LM3TzRlzGlt5fIGO5UnaR6UL1ZVfB9RRFjxQFQ6awDrwAc6Km8Y5gD8pInuwYPF+6/TiCxXA==", "license": "MIT", "dependencies": { "fs-extra": "^11.1.1", @@ -4935,8 +5041,6 @@ }, "node_modules/@docusaurus/types": { "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.10.2.tgz", - "integrity": "sha512-B6rvfwIFSapUqUJjMriZswX13K8l5Z7AcmVE6uTEJpYddQieSTR12DsGaFtcZAIDsQd4p+0WTl0Vc6jmZK0Trw==", "license": "MIT", "dependencies": { "@mdx-js/mdx": "^3.0.0", @@ -4957,8 +5061,6 @@ }, "node_modules/@docusaurus/types/node_modules/commander": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", "license": "MIT", "engines": { "node": ">= 6" @@ -4966,8 +5068,6 @@ }, "node_modules/@docusaurus/types/node_modules/webpack-merge": { "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", @@ -4980,9 +5080,8 @@ }, "node_modules/@docusaurus/utils": { "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.10.2.tgz", - "integrity": "sha512-xx0W3eav2uW1NRIpuHJWNwLTC15xPNjU4Uxi9NSnd3swYC96BE3vFiT93SD8s24kmAAWNwgZwfZ2fghGZ01Lcw==", "license": "MIT", + "peer": true, "dependencies": { "@11ty/gray-matter": "^1.0.0", "@docusaurus/logger": "3.10.2", @@ -5012,513 +5111,142 @@ }, "node_modules/@docusaurus/utils-common": { "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.10.2.tgz", - "integrity": "sha512-x3Dz6jv6iQKBNjBmVTu8p57abMp/VNTUgKBMgRVXJc5444orBTsArv0+cdfrXTiz/VMmHfDRVkPbL7GH2B7T7w==", "license": "MIT", "dependencies": { "@docusaurus/types": "3.10.2", "tslib": "^2.6.0" }, "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/utils-validation": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.10.2.tgz", - "integrity": "sha512-sn8unbDfUL585NtR3cwHefPicOyaHvPaX7VD0aOg/siIxUBoKyKKaGEqzJZDS64mM43TnxurkYDtmB1wsJlZsw==", - "license": "MIT", - "dependencies": { - "@docusaurus/logger": "3.10.2", - "@docusaurus/utils": "3.10.2", - "@docusaurus/utils-common": "3.10.2", - "fs-extra": "^11.2.0", - "joi": "^17.9.2", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@easyops-cn/autocomplete.js": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/@easyops-cn/autocomplete.js/-/autocomplete.js-0.38.1.tgz", - "integrity": "sha512-drg76jS6syilOUmVNkyo1c7ZEBPcPuK+aJA7AksM5ZIIbV57DMHCywiCr+uHyv8BE5jUTU98j/H7gVrkHrWW3Q==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "immediate": "^3.2.3" - } - }, - "node_modules/@easyops-cn/docusaurus-search-local": { - "version": "0.55.3", - "resolved": "https://registry.npmjs.org/@easyops-cn/docusaurus-search-local/-/docusaurus-search-local-0.55.3.tgz", - "integrity": "sha512-PlMKmxuonvZ1C+/zJS1hJaF1xaxD1TsUvOMzpP6DdQMtZqmTaYjcLGM12ePzl0m1rp3AgfTBeDbFNHQhwnH/dA==", - "license": "MIT", - "dependencies": { - "@docusaurus/plugin-content-docs": "^2 || ^3", - "@docusaurus/theme-translations": "^2 || ^3", - "@docusaurus/utils": "^2 || ^3", - "@docusaurus/utils-common": "^2 || ^3", - "@docusaurus/utils-validation": "^2 || ^3", - "@easyops-cn/autocomplete.js": "^0.38.1", - "@node-rs/jieba": "^1.6.0", - "cheerio": "^1.0.0", - "clsx": "^2.1.1", - "comlink": "^4.4.2", - "debug": "^4.2.0", - "fs-extra": "^10.0.0", - "klaw-sync": "^6.0.0", - "lunr": "^2.3.9", - "lunr-languages": "^1.4.0", - "mark.js": "^8.11.1", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "@docusaurus/theme-common": "^2 || ^3", - "open-ask-ai": "^0.7.3", - "react": "^16.14.0 || ^17 || ^18 || ^19", - "react-dom": "^16.14.0 || 17 || ^18 || ^19" - }, - "peerDependenciesMeta": { - "open-ask-ai": { - "optional": true - } - } - }, - "node_modules/@easyops-cn/docusaurus-search-local/node_modules/cheerio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", - "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.1", - "htmlparser2": "^10.1.0", - "parse5": "^7.3.0", - "parse5-htmlparser2-tree-adapter": "^7.1.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^7.19.0", - "whatwg-mimetype": "^4.0.0" - }, - "engines": { - "node": ">=20.18.1" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/@easyops-cn/docusaurus-search-local/node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/@easyops-cn/docusaurus-search-local/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@easyops-cn/docusaurus-search-local/node_modules/htmlparser2": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", - "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "entities": "^7.0.1" - } - }, - "node_modules/@emnapi/core": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz", - "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.3", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", - "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", - "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", - "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", - "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", - "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", - "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", - "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", - "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", - "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", - "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", - "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", - "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", - "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", - "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", - "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "node": ">=20.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", - "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", - "cpu": [ - "ppc64" - ], - "dev": true, + "node_modules/@docusaurus/utils-validation": { + "version": "3.10.2", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@docusaurus/logger": "3.10.2", + "@docusaurus/utils": "3.10.2", + "@docusaurus/utils-common": "3.10.2", + "fs-extra": "^11.2.0", + "joi": "^17.9.2", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "tslib": "^2.6.0" + }, "engines": { - "node": ">=18" + "node": ">=20.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", - "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", - "cpu": [ - "riscv64" - ], - "dev": true, + "node_modules/@easyops-cn/autocomplete.js": { + "version": "0.38.1", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "cssesc": "^3.0.0", + "immediate": "^3.2.3" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", - "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", - "cpu": [ - "s390x" - ], - "dev": true, + "node_modules/@easyops-cn/docusaurus-search-local": { + "version": "0.55.3", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@docusaurus/plugin-content-docs": "^2 || ^3", + "@docusaurus/theme-translations": "^2 || ^3", + "@docusaurus/utils": "^2 || ^3", + "@docusaurus/utils-common": "^2 || ^3", + "@docusaurus/utils-validation": "^2 || ^3", + "@easyops-cn/autocomplete.js": "^0.38.1", + "@node-rs/jieba": "^1.6.0", + "cheerio": "^1.0.0", + "clsx": "^2.1.1", + "comlink": "^4.4.2", + "debug": "^4.2.0", + "fs-extra": "^10.0.0", + "klaw-sync": "^6.0.0", + "lunr": "^2.3.9", + "lunr-languages": "^1.4.0", + "mark.js": "^8.11.1", + "tslib": "^2.4.0" + }, "engines": { - "node": ">=18" + "node": ">=12" + }, + "peerDependencies": { + "@docusaurus/theme-common": "^2 || ^3", + "open-ask-ai": "^0.7.3", + "react": "^16.14.0 || ^17 || ^18 || ^19", + "react-dom": "^16.14.0 || 17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "open-ask-ai": { + "optional": true + } } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", - "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@easyops-cn/docusaurus-search-local/node_modules/cheerio": { + "version": "1.2.0", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, "engines": { - "node": ">=18" + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", - "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@easyops-cn/docusaurus-search-local/node_modules/entities": { + "version": "7.0.1", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@easyops-cn/docusaurus-search-local/node_modules/fs-extra": { + "version": "10.1.0", "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", - "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", - "cpu": [ - "x64" + "node_modules/@easyops-cn/docusaurus-search-local/node_modules/htmlparser2": { + "version": "10.1.0", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } ], - "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" } }, - "node_modules/@esbuild/openbsd-arm64": { + "node_modules/@esbuild/darwin-arm64": { "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", - "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", "cpu": [ "arm64" ], @@ -5526,118 +5254,119 @@ "license": "MIT", "optional": true, "os": [ - "openbsd" + "darwin" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", - "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", - "cpu": [ - "x64" - ], + "node_modules/@eslint/eslintrc": { + "version": "3.3.6", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.0", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, "engines": { - "node": ">=18" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", - "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", - "cpu": [ - "arm64" - ], + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.15.0", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", - "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", - "cpu": [ - "x64" - ], + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", - "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", - "cpu": [ - "arm64" - ], + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=18" + "node": "*" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", - "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", - "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", - "cpu": [ - "x64" - ], + "node_modules/@eth-optimism/core-utils": { + "version": "0.12.0", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/contracts": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/providers": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0", + "bufio": "^1.0.7", + "chai": "^4.3.4" } }, "node_modules/@ethers-ext/signer-ledger": { "version": "6.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@ethers-ext/signer-ledger/-/signer-ledger-6.0.0-beta.1.tgz", - "integrity": "sha512-4A7O1J4eZ6tNAxAuR1jWPD7UkR+QU8gc8ClLqdNGCevwlwRLbOt/lQGp27f/AWrof55CIE2wxTB9Lpex6FfuDw==", "funding": [ { "type": "individual", @@ -5655,8 +5384,6 @@ }, "node_modules/@ethersproject/abi": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.8.0.tgz", - "integrity": "sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==", "funding": [ { "type": "individual", @@ -5682,8 +5409,6 @@ }, "node_modules/@ethersproject/abstract-provider": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.8.0.tgz", - "integrity": "sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==", "funding": [ { "type": "individual", @@ -5707,8 +5432,6 @@ }, "node_modules/@ethersproject/abstract-signer": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.8.0.tgz", - "integrity": "sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==", "funding": [ { "type": "individual", @@ -5730,8 +5453,6 @@ }, "node_modules/@ethersproject/address": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.8.0.tgz", - "integrity": "sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==", "funding": [ { "type": "individual", @@ -5753,8 +5474,6 @@ }, "node_modules/@ethersproject/base64": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.8.0.tgz", - "integrity": "sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==", "funding": [ { "type": "individual", @@ -5770,10 +5489,27 @@ "@ethersproject/bytes": "^5.8.0" } }, + "node_modules/@ethersproject/basex": { + "version": "5.8.0", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/properties": "^5.8.0" + } + }, "node_modules/@ethersproject/bignumber": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.8.0.tgz", - "integrity": "sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==", "funding": [ { "type": "individual", @@ -5793,8 +5529,6 @@ }, "node_modules/@ethersproject/bytes": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz", - "integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==", "funding": [ { "type": "individual", @@ -5812,8 +5546,6 @@ }, "node_modules/@ethersproject/constants": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.8.0.tgz", - "integrity": "sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==", "funding": [ { "type": "individual", @@ -5829,10 +5561,35 @@ "@ethersproject/bignumber": "^5.8.0" } }, + "node_modules/@ethersproject/contracts": { + "version": "5.8.0", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abi": "^5.8.0", + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/transactions": "^5.8.0" + } + }, "node_modules/@ethersproject/hash": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.8.0.tgz", - "integrity": "sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==", "funding": [ { "type": "individual", @@ -5856,10 +5613,72 @@ "@ethersproject/strings": "^5.8.0" } }, + "node_modules/@ethersproject/hdnode": { + "version": "5.8.0", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/basex": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/pbkdf2": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/sha2": "^5.8.0", + "@ethersproject/signing-key": "^5.8.0", + "@ethersproject/strings": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/wordlists": "^5.8.0" + } + }, + "node_modules/@ethersproject/json-wallets": { + "version": "5.8.0", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/hdnode": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/pbkdf2": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/random": "^5.8.0", + "@ethersproject/strings": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, + "node_modules/@ethersproject/json-wallets/node_modules/aes-js": { + "version": "3.0.0", + "dev": true, + "license": "MIT" + }, "node_modules/@ethersproject/keccak256": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.8.0.tgz", - "integrity": "sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==", "funding": [ { "type": "individual", @@ -5878,8 +5697,6 @@ }, "node_modules/@ethersproject/logger": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz", - "integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==", "funding": [ { "type": "individual", @@ -5894,8 +5711,6 @@ }, "node_modules/@ethersproject/networks": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.8.0.tgz", - "integrity": "sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==", "funding": [ { "type": "individual", @@ -5911,10 +5726,27 @@ "@ethersproject/logger": "^5.8.0" } }, + "node_modules/@ethersproject/pbkdf2": { + "version": "5.8.0", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/sha2": "^5.8.0" + } + }, "node_modules/@ethersproject/properties": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.8.0.tgz", - "integrity": "sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==", "funding": [ { "type": "individual", @@ -5930,10 +5762,64 @@ "@ethersproject/logger": "^5.8.0" } }, + "node_modules/@ethersproject/providers": { + "version": "5.8.0", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/base64": "^5.8.0", + "@ethersproject/basex": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/networks": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/random": "^5.8.0", + "@ethersproject/rlp": "^5.8.0", + "@ethersproject/sha2": "^5.8.0", + "@ethersproject/strings": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/web": "^5.8.0", + "bech32": "1.1.4", + "ws": "8.18.0" + } + }, + "node_modules/@ethersproject/random": { + "version": "5.8.0", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, "node_modules/@ethersproject/rlp": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.8.0.tgz", - "integrity": "sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==", "funding": [ { "type": "individual", @@ -5950,10 +5836,28 @@ "@ethersproject/logger": "^5.8.0" } }, + "node_modules/@ethersproject/sha2": { + "version": "5.8.0", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "hash.js": "1.1.7" + } + }, "node_modules/@ethersproject/signing-key": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.8.0.tgz", - "integrity": "sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==", "funding": [ { "type": "individual", @@ -5974,10 +5878,76 @@ "hash.js": "1.1.7" } }, - "node_modules/@ethersproject/strings": { + "node_modules/@ethersproject/solidity": { + "version": "5.8.0", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/sha2": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.8.0", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/rlp": "^5.8.0", + "@ethersproject/signing-key": "^5.8.0" + } + }, + "node_modules/@ethersproject/units": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.8.0.tgz", - "integrity": "sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==", + "dev": true, "funding": [ { "type": "individual", @@ -5990,15 +5960,14 @@ ], "license": "MIT", "dependencies": { - "@ethersproject/bytes": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", "@ethersproject/constants": "^5.8.0", "@ethersproject/logger": "^5.8.0" } }, - "node_modules/@ethersproject/transactions": { + "node_modules/@ethersproject/wallet": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.8.0.tgz", - "integrity": "sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==", + "dev": true, "funding": [ { "type": "individual", @@ -6011,21 +5980,25 @@ ], "license": "MIT", "dependencies": { + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/abstract-signer": "^5.8.0", "@ethersproject/address": "^5.8.0", "@ethersproject/bignumber": "^5.8.0", "@ethersproject/bytes": "^5.8.0", - "@ethersproject/constants": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/hdnode": "^5.8.0", + "@ethersproject/json-wallets": "^5.8.0", "@ethersproject/keccak256": "^5.8.0", "@ethersproject/logger": "^5.8.0", "@ethersproject/properties": "^5.8.0", - "@ethersproject/rlp": "^5.8.0", - "@ethersproject/signing-key": "^5.8.0" + "@ethersproject/random": "^5.8.0", + "@ethersproject/signing-key": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/wordlists": "^5.8.0" } }, "node_modules/@ethersproject/web": { "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.8.0.tgz", - "integrity": "sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==", "funding": [ { "type": "individual", @@ -6045,23 +6018,38 @@ "@ethersproject/strings": "^5.8.0" } }, + "node_modules/@ethersproject/wordlists": { + "version": "5.8.0", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, "node_modules/@exodus/schemasafe": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", - "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==", "license": "MIT" }, "node_modules/@faker-js/faker": { "version": "5.5.3", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-5.5.3.tgz", - "integrity": "sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==", - "deprecated": "Please update to a newer version.", "license": "MIT" }, "node_modules/@gerrit0/mini-shiki": { "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.23.0.tgz", - "integrity": "sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==", "dev": true, "license": "MIT", "dependencies": { @@ -6074,8 +6062,6 @@ }, "node_modules/@gql.tada/cli-utils": { "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@gql.tada/cli-utils/-/cli-utils-1.9.3.tgz", - "integrity": "sha512-P1TiXErpJwIi73sei5fzwGA/SOeCaIHFWFR4RdZPLwqxZzQN0T6MAUivzXBgKCORL67rvYnLaaPoWeqWq/61ug==", "license": "MIT", "dependencies": { "@0no-co/graphqlsp": "^1.17.3", @@ -6100,8 +6086,6 @@ }, "node_modules/@gql.tada/internal": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@gql.tada/internal/-/internal-1.2.2.tgz", - "integrity": "sha512-4lZcElPP6MC8Ct8KN70LR2WQsHjbAsyAmTGG09VsOPhx738UFIYaL0S1XiI2pqq2tj/sDs/y3b9jvKoWGL7iuQ==", "license": "MIT", "dependencies": { "@0no-co/graphql.web": "^1.3.1" @@ -6113,8 +6097,6 @@ }, "node_modules/@graphql-typed-document-node/core": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", - "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", "license": "MIT", "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" @@ -6122,8 +6104,6 @@ }, "node_modules/@hapi/address": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz", - "integrity": "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==", "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^11.0.2" @@ -6134,26 +6114,18 @@ }, "node_modules/@hapi/formula": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-3.0.2.tgz", - "integrity": "sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==", "license": "BSD-3-Clause" }, "node_modules/@hapi/hoek": { "version": "11.0.7", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", - "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", "license": "BSD-3-Clause" }, "node_modules/@hapi/pinpoint": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz", - "integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==", "license": "BSD-3-Clause" }, "node_modules/@hapi/tlds": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.7.tgz", - "integrity": "sha512-MgNjRwy9Ti92yVAixLmDc8dd1bJIKwO9qlWCfFQRwRmUEDPQHYn4G6hwPFvFGUTzAa0FsS+inMjLin7GnyBRhA==", "license": "BSD-3-Clause", "engines": { "node": ">=14.0.0" @@ -6161,8 +6133,6 @@ }, "node_modules/@hapi/topo": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", - "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^11.0.2" @@ -6170,8 +6140,6 @@ }, "node_modules/@hookform/error-message": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@hookform/error-message/-/error-message-2.0.1.tgz", - "integrity": "sha512-U410sAr92xgxT1idlu9WWOVjndxLdgPUHEB8Schr27C9eh7/xUnITWpCMF93s+lGiG++D4JnbSnrb5A21AdSNg==", "license": "MIT", "peerDependencies": { "react": ">=16.8.0", @@ -6181,14 +6149,10 @@ }, "node_modules/@iconify/types": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", "license": "MIT" }, "node_modules/@iconify/utils": { "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.4.tgz", - "integrity": "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==", "license": "MIT", "dependencies": { "@antfu/install-pkg": "^1.1.0", @@ -6526,8 +6490,6 @@ }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "dev": true, "license": "ISC", "dependencies": { @@ -6539,8 +6501,6 @@ }, "node_modules/@istanbuljs/schema": { "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", - "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, "license": "MIT", "engines": { @@ -6549,8 +6509,6 @@ }, "node_modules/@jest/schemas": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" @@ -6561,8 +6519,6 @@ }, "node_modules/@jest/types": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -6578,8 +6534,6 @@ }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -6588,8 +6542,6 @@ }, "node_modules/@jridgewell/remapping": { "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -6598,8 +6550,6 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -6607,8 +6557,6 @@ }, "node_modules/@jridgewell/source-map": { "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -6616,15 +6564,11 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", - "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "version": "1.5.5", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -6633,8 +6577,6 @@ }, "node_modules/@jsonjoy.com/base64": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", "license": "Apache-2.0", "engines": { "node": ">=10.0" @@ -6649,8 +6591,6 @@ }, "node_modules/@jsonjoy.com/buffers": { "version": "17.67.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", - "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", "license": "Apache-2.0", "engines": { "node": ">=10.0" @@ -6665,8 +6605,6 @@ }, "node_modules/@jsonjoy.com/codegen": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", - "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", "license": "Apache-2.0", "engines": { "node": ">=10.0" @@ -6680,13 +6618,11 @@ } }, "node_modules/@jsonjoy.com/fs-core": { - "version": "4.69.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.69.1.tgz", - "integrity": "sha512-ohxv/4KZSG03UmjDo//eIjedRRQEcIc0w4IjbyBcMS4mdkvTVMQwfNm1Ivxj4z35xIwpQbJ+1sGKq3l4vTaFiA==", + "version": "4.68.1", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-builtins": "4.69.1", - "@jsonjoy.com/fs-node-utils": "4.69.1", + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "@jsonjoy.com/fs-node-utils": "4.68.1", "thingies": "^2.5.0" }, "engines": { @@ -6701,14 +6637,12 @@ } }, "node_modules/@jsonjoy.com/fs-fsa": { - "version": "4.69.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.69.1.tgz", - "integrity": "sha512-z9IfdQyZGXL9GB9gv+dXRuMjOjY8s2EmFQ8DhdxxjFJGfPITDDW1j/W84jlBr2SmCc+yv47yYiNQ3powkqWGVQ==", + "version": "4.68.1", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.69.1", - "@jsonjoy.com/fs-node-builtins": "4.69.1", - "@jsonjoy.com/fs-node-utils": "4.69.1", + "@jsonjoy.com/fs-core": "4.68.1", + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "@jsonjoy.com/fs-node-utils": "4.68.1", "thingies": "^2.5.0" }, "engines": { @@ -6723,16 +6657,14 @@ } }, "node_modules/@jsonjoy.com/fs-node": { - "version": "4.69.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.69.1.tgz", - "integrity": "sha512-oIIXv5EfAqj2Wsck9dDa5njbTyCpef4OV+W1h7cYrlph0rRIay91DIhNk+OdiwLgKHoqyBmUWRy3GXAHGiXQZw==", + "version": "4.68.1", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.69.1", - "@jsonjoy.com/fs-node-builtins": "4.69.1", - "@jsonjoy.com/fs-node-utils": "4.69.1", - "@jsonjoy.com/fs-print": "4.69.1", - "@jsonjoy.com/fs-snapshot": "4.69.1", + "@jsonjoy.com/fs-core": "4.68.1", + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "@jsonjoy.com/fs-node-utils": "4.68.1", + "@jsonjoy.com/fs-print": "4.68.1", + "@jsonjoy.com/fs-snapshot": "4.68.1", "glob-to-regex.js": "^1.0.0", "thingies": "^2.5.0" }, @@ -6748,9 +6680,7 @@ } }, "node_modules/@jsonjoy.com/fs-node-builtins": { - "version": "4.69.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.69.1.tgz", - "integrity": "sha512-nJddTKNx2FrH+fNRHcHJpCD8bwZlQmYaT8SPEG86kKUae5XzWU5hXdk9T4vKU9yBoXfT2po1A3K/twf8ujBd0g==", + "version": "4.68.1", "license": "Apache-2.0", "engines": { "node": ">=10.0" @@ -6764,14 +6694,12 @@ } }, "node_modules/@jsonjoy.com/fs-node-to-fsa": { - "version": "4.69.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.69.1.tgz", - "integrity": "sha512-1GTORTbZ2GcshGyaGNgqJAm/INOWyTdHMbeuj2CsBWKYEQYHW4ZvkKHaR+46SO762skZcNVja3dLIfk3s1AdcQ==", + "version": "4.68.1", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-fsa": "4.69.1", - "@jsonjoy.com/fs-node-builtins": "4.69.1", - "@jsonjoy.com/fs-node-utils": "4.69.1" + "@jsonjoy.com/fs-fsa": "4.68.1", + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "@jsonjoy.com/fs-node-utils": "4.68.1" }, "engines": { "node": ">=10.0" @@ -6785,12 +6713,10 @@ } }, "node_modules/@jsonjoy.com/fs-node-utils": { - "version": "4.69.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.69.1.tgz", - "integrity": "sha512-45kAGpkTUKBNRguS34fdBkXbkBuZM75oU+snf3kRjfQGWHmp93aNKHdtbqrVqxpOdOvWAFnMx+UN6rRSLN2p9g==", + "version": "4.68.1", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-builtins": "4.69.1", + "@jsonjoy.com/fs-node-builtins": "4.68.1", "glob-to-regex.js": "^1.0.1" }, "engines": { @@ -6805,12 +6731,10 @@ } }, "node_modules/@jsonjoy.com/fs-print": { - "version": "4.69.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.69.1.tgz", - "integrity": "sha512-As9V7Ra/mHsjHQicor5PTTymcqPb2QRTonedIsMuqMz2JEAZIFdRy+qvi5O15lIe88fASxHQXLIU6H5G01kfxg==", + "version": "4.68.1", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-utils": "4.69.1", + "@jsonjoy.com/fs-node-utils": "4.68.1", "tree-dump": "^1.1.0" }, "engines": { @@ -6825,13 +6749,11 @@ } }, "node_modules/@jsonjoy.com/fs-snapshot": { - "version": "4.69.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.69.1.tgz", - "integrity": "sha512-wUffyQdpDSh5RVA+oubKLMcorJfpxUia+ETYPnK6LK+myJ4YVDKWX4UMSjUCapvbhjZRxr6tvwYh4zE3aBpAuA==", + "version": "4.68.1", "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/buffers": "^17.65.0", - "@jsonjoy.com/fs-node-utils": "4.69.1", + "@jsonjoy.com/fs-node-utils": "4.68.1", "@jsonjoy.com/json-pack": "^17.65.0", "@jsonjoy.com/util": "^17.65.0" }, @@ -6848,8 +6770,6 @@ }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { "version": "17.67.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", - "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", "license": "Apache-2.0", "engines": { "node": ">=10.0" @@ -6864,8 +6784,6 @@ }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { "version": "17.67.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", - "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", "license": "Apache-2.0", "engines": { "node": ">=10.0" @@ -6880,8 +6798,6 @@ }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { "version": "17.67.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", - "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/base64": "17.67.0", @@ -6906,8 +6822,6 @@ }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { "version": "17.67.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", - "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/util": "17.67.0" @@ -6925,8 +6839,6 @@ }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { "version": "17.67.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", - "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/buffers": "17.67.0", @@ -6945,8 +6857,6 @@ }, "node_modules/@jsonjoy.com/json-pack": { "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", - "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/base64": "^1.1.2", @@ -6971,8 +6881,6 @@ }, "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", - "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", "license": "Apache-2.0", "engines": { "node": ">=10.0" @@ -6987,8 +6895,6 @@ }, "node_modules/@jsonjoy.com/json-pointer": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", - "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/codegen": "^1.0.0", @@ -7007,8 +6913,6 @@ }, "node_modules/@jsonjoy.com/util": { "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", - "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/buffers": "^1.0.0", @@ -7027,8 +6931,6 @@ }, "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", - "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", "license": "Apache-2.0", "engines": { "node": ">=10.0" @@ -7049,8 +6951,6 @@ }, "node_modules/@ledgerhq/cryptoassets": { "version": "9.13.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/cryptoassets/-/cryptoassets-9.13.0.tgz", - "integrity": "sha512-MzGJyc48OGU/FLYGYwEJyfOgbJzlR8XJ9Oo6XpNpNUM1/E5NDqvD72V0D+0uWIJYN3e2NtyqHXShLZDu7P95YA==", "license": "Apache-2.0", "dependencies": { "invariant": "2" @@ -7058,8 +6958,6 @@ }, "node_modules/@ledgerhq/devices": { "version": "8.12.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.12.0.tgz", - "integrity": "sha512-E6msvdhwHax6mseoOzuPp/z7+X41KE2PWBzmmmrrJ+pNNp7KIb/FZbGuwNu1Vjeg3ekbExVwQYJXdwQLuGwRKw==", "license": "Apache-2.0", "dependencies": { "@ledgerhq/errors": "^6.31.0", @@ -7070,8 +6968,6 @@ }, "node_modules/@ledgerhq/devices/node_modules/semver": { "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -7081,35 +6977,45 @@ } }, "node_modules/@ledgerhq/domain-service": { - "version": "1.8.16", - "resolved": "https://registry.npmjs.org/@ledgerhq/domain-service/-/domain-service-1.8.16.tgz", - "integrity": "sha512-T9RVXi0Toepe+C2HV8iv1scximAXFErvGcBtAs3LZSacAwHSWcBZlmm0SxT8jNt4dYsMQo1UVYjsY7oh2BnU3w==", + "version": "1.8.15", "license": "Apache-2.0", "dependencies": { "@ledgerhq/logs": "6.17.0", - "@ledgerhq/types-live": "^6.121.0", + "@ledgerhq/types-live": "^6.120.0", "axios": "1.13.5", "eip55": "^2.1.1", "react": "19.1.4", "react-dom": "19.1.4" } }, - "node_modules/@ledgerhq/domain-service/node_modules/@ledgerhq/logs": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/logs/-/logs-6.17.0.tgz", - "integrity": "sha512-yra33g5q/AU7+PwAws+GaVpQGUuxnDREjVBnviJjcaJLVKuLzI4pnj8Bd3nY3fypM5k1yZEYKEXfUuGFUjP2+w==", - "license": "Apache-2.0" + "node_modules/@ledgerhq/domain-service/node_modules/react": { + "version": "19.1.4", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ledgerhq/domain-service/node_modules/react-dom": { + "version": "19.1.4", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.4" + } + }, + "node_modules/@ledgerhq/domain-service/node_modules/scheduler": { + "version": "0.26.0", + "license": "MIT" }, "node_modules/@ledgerhq/errors": { "version": "6.37.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-6.37.0.tgz", - "integrity": "sha512-T5yiKI5UX7ugeocdTF3TUsCIN2BH41Bio4ZeN410YFjFOf3es08n/5JyMzzKwzRgP0blG3HfBf7s7vJKqCSAeg==", "license": "Apache-2.0" }, "node_modules/@ledgerhq/hw-app-aptos": { "version": "6.37.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/hw-app-aptos/-/hw-app-aptos-6.37.0.tgz", - "integrity": "sha512-+06THws7VWbO27a+nrdje2GLrxBrQKXjw/iZPSVVCbnjoriWVuVr4QKwxI9VKaCqLJ/5X66M8/dHQ4eNKtXOHg==", "license": "Apache-2.0", "dependencies": { "@ledgerhq/errors": "^6.31.0", @@ -7120,8 +7026,6 @@ }, "node_modules/@ledgerhq/hw-app-aptos/node_modules/@noble/hashes": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -7132,8 +7036,6 @@ }, "node_modules/@ledgerhq/hw-app-eth": { "version": "6.33.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/hw-app-eth/-/hw-app-eth-6.33.0.tgz", - "integrity": "sha512-YwKHoL3YPzqc5P/Y4f+/hvs9IxrXxKdBwm4FCobQEwY3+YMHj/HCUlNcALuoHTXsDGDfTIL0q3FipbNij70IGg==", "license": "Apache-2.0", "dependencies": { "@ethersproject/abi": "^5.5.0", @@ -7151,8 +7053,6 @@ }, "node_modules/@ledgerhq/hw-app-solana": { "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/hw-app-solana/-/hw-app-solana-7.9.0.tgz", - "integrity": "sha512-M04bQ/vPIgoQzJBo3HDu4oLCerIjlYdNA3N9ciS37jeZSuEq978VgZhy+KMxdpnY2zfru32inMmOWQr302EBuw==", "license": "Apache-2.0", "dependencies": { "@ledgerhq/errors": "^6.31.0", @@ -7162,8 +7062,6 @@ }, "node_modules/@ledgerhq/hw-transport": { "version": "6.34.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.34.0.tgz", - "integrity": "sha512-BHpsf2n0/JnHsGSLGT3x3dEgOk7oLO3QYXYrzrn4RVDdiYAEyXbERYJeS1D4WbINN0/LBmTM0n4M8raRJBxxSg==", "license": "Apache-2.0", "dependencies": { "@ledgerhq/devices": "8.12.0", @@ -7174,8 +7072,6 @@ }, "node_modules/@ledgerhq/hw-transport-mocker": { "version": "6.34.7", - "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-mocker/-/hw-transport-mocker-6.34.7.tgz", - "integrity": "sha512-AyaO4unEHhZbyyo9y16z36uOi9pqY6kqVtWhTAp1PLZuQhXR/Y0m+Yd3yJrjUvlNpIOrrVN1rA+gHlmewl43Og==", "license": "Apache-2.0", "dependencies": { "@ledgerhq/hw-transport": "6.35.7", @@ -7185,8 +7081,6 @@ }, "node_modules/@ledgerhq/hw-transport-mocker/node_modules/@ledgerhq/devices": { "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.17.0.tgz", - "integrity": "sha512-l+rrVQEjR1hSWOLD00LFX4zbS8yB1M/Mb6UYPmSHGO7TmE1CFbZ4CmDJC/kZSl3hdrXCJ+YUNpvaLCcSWDwA+Q==", "license": "Apache-2.0", "dependencies": { "semver": "7.7.3" @@ -7194,14 +7088,10 @@ }, "node_modules/@ledgerhq/hw-transport-mocker/node_modules/@ledgerhq/errors": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-7.0.0.tgz", - "integrity": "sha512-+Q/vykUlNeIxiM+I3cu1B660WLkzlmIsHLTV9QNV5D2/Ocplx3QMg52NYq2X7OAfGQnfH1rQvhn/NrjT+t9wBA==", "license": "Apache-2.0" }, "node_modules/@ledgerhq/hw-transport-mocker/node_modules/@ledgerhq/hw-transport": { "version": "6.35.7", - "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.35.7.tgz", - "integrity": "sha512-vVhAVQ56+7A5FY5Mr09HY+bmf3H6TXpwsj+/xbadNkjl//e/YzTWfpXk4eBnj3hwk1PQ9Mn6pKFH0BHjS2BKlg==", "license": "Apache-2.0", "dependencies": { "@ledgerhq/devices": "8.17.0", @@ -7212,8 +7102,6 @@ }, "node_modules/@ledgerhq/hw-transport-mocker/node_modules/semver": { "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -7224,8 +7112,6 @@ }, "node_modules/@ledgerhq/hw-transport-node-hid": { "version": "6.32.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-6.32.0.tgz", - "integrity": "sha512-UZ1nKEaOfj8P3lT7tc6a7Da+nEymxlNR/zya4+TktL0TSPcs/UDtYo77Yx2KxhXFtBTEX3GJ3EfxVpkzPy90sw==", "license": "Apache-2.0", "dependencies": { "@ledgerhq/devices": "8.12.0", @@ -7240,8 +7126,6 @@ }, "node_modules/@ledgerhq/hw-transport-node-hid-noevents": { "version": "6.36.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-6.36.0.tgz", - "integrity": "sha512-hsqybDCDtpNWeWVY2b/emrsFDqVfc3Rkv4+2b6h5ubPGsKWFd+7t0u6oy0sJEl5v6C+V/uxl03shBjWEcSoxgw==", "license": "Apache-2.0", "dependencies": { "@ledgerhq/devices": "8.16.0", @@ -7253,8 +7137,6 @@ }, "node_modules/@ledgerhq/hw-transport-node-hid-noevents/node_modules/@ledgerhq/devices": { "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.16.0.tgz", - "integrity": "sha512-brXLPzkvGM3D5YNsWQ25P5G4SmWdSNBed9W8wKoOIRLGdRfvE+bg9mzFty0iZ+aRLBkLoXwX7xKIL9zUi6LBKQ==", "license": "Apache-2.0", "dependencies": { "semver": "7.7.3" @@ -7262,8 +7144,6 @@ }, "node_modules/@ledgerhq/hw-transport-node-hid-noevents/node_modules/@ledgerhq/hw-transport": { "version": "6.35.5", - "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.35.5.tgz", - "integrity": "sha512-P4+wtLewLWgxPtIb90h5kjpzXVlC6f4IBQBmvowVFkInvZt34ffXkX7wa5KfMzu4l3cqCcpNSqtPSCMp+0vuqg==", "license": "Apache-2.0", "dependencies": { "@ledgerhq/devices": "8.16.0", @@ -7274,8 +7154,6 @@ }, "node_modules/@ledgerhq/hw-transport-node-hid-noevents/node_modules/semver": { "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -7285,15 +7163,11 @@ } }, "node_modules/@ledgerhq/logs": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/logs/-/logs-6.19.0.tgz", - "integrity": "sha512-kmWj18C7eTtuytdaWygzVrQm9EoD4YWuYnd/kY8stVBBAzRdJzrGlm013ijQF6I/kGLKlGY7JKmll6j32KGpfQ==", + "version": "6.17.0", "license": "Apache-2.0" }, "node_modules/@ledgerhq/types-live": { - "version": "6.121.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/types-live/-/types-live-6.121.0.tgz", - "integrity": "sha512-szbhoK0/Kn5kKTKhjJW5Bhg2uh5tdQ3wkVsoZnjBZKO2YiveloW9K6Yp3mfWicQBZuAn6CAO40RKEe4d0oIAig==", + "version": "6.120.0", "license": "Apache-2.0", "dependencies": { "bignumber.js": "^9.1.2", @@ -7302,14 +7176,150 @@ }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", "license": "MIT" }, + "node_modules/@manypkg/find-root": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@types/node": "^12.7.1", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0" + } + }, + "node_modules/@manypkg/find-root/node_modules/@types/node": { + "version": "12.20.55", + "dev": true, + "license": "MIT" + }, + "node_modules/@manypkg/find-root/node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@manypkg/find-root/node_modules/fs-extra": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@manypkg/find-root/node_modules/jsonfile": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@manypkg/find-root/node_modules/locate-path": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@manypkg/find-root/node_modules/p-limit": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@manypkg/find-root/node_modules/p-locate": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@manypkg/find-root/node_modules/universalify": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@manypkg/get-packages": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@changesets/types": "^4.0.1", + "@manypkg/find-root": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "^11.0.0", + "read-yaml-file": "^1.1.0" + } + }, + "node_modules/@manypkg/get-packages/node_modules/@changesets/types": { + "version": "4.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@manypkg/get-packages/node_modules/fs-extra": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@manypkg/get-packages/node_modules/jsonfile": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@manypkg/get-packages/node_modules/universalify": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/@mdx-js/mdx": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", - "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -7343,36 +7353,200 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/@mdx-js/react": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", - "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", + "node_modules/@mdx-js/react": { + "version": "3.1.1", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/@mermaid-js/parser": { + "version": "1.2.1", + "license": "MIT", + "dependencies": { + "@chevrotain/types": "~11.1.2" + } + }, + "node_modules/@metaplex-foundation/mpl-token-metadata": { + "version": "3.4.0", + "license": "Apache-2.0", + "dependencies": { + "@metaplex-foundation/mpl-toolbox": "^0.10.0" + }, + "peerDependencies": { + "@metaplex-foundation/umi": ">= 0.8.2 <= 1" + } + }, + "node_modules/@metaplex-foundation/mpl-toolbox": { + "version": "0.10.0", + "license": "Apache-2.0", + "peerDependencies": { + "@metaplex-foundation/umi": ">= 0.8.2 <= 1" + } + }, + "node_modules/@metaplex-foundation/umi": { + "version": "1.5.1", + "license": "MIT", + "peer": true, + "dependencies": { + "@metaplex-foundation/umi-options": "^1.5.1", + "@metaplex-foundation/umi-public-keys": "^1.5.1", + "@metaplex-foundation/umi-serializers": "^1.5.1" + } + }, + "node_modules/@metaplex-foundation/umi-bundle-defaults": { + "version": "1.5.1", + "license": "MIT", + "dependencies": { + "@metaplex-foundation/umi-downloader-http": "^1.5.1", + "@metaplex-foundation/umi-eddsa-web3js": "^1.5.1", + "@metaplex-foundation/umi-http-fetch": "^1.5.1", + "@metaplex-foundation/umi-program-repository": "^1.5.1", + "@metaplex-foundation/umi-rpc-chunk-get-accounts": "^1.5.1", + "@metaplex-foundation/umi-rpc-web3js": "^1.5.1", + "@metaplex-foundation/umi-serializer-data-view": "^1.5.1", + "@metaplex-foundation/umi-transaction-factory-web3js": "^1.5.1" + }, + "peerDependencies": { + "@metaplex-foundation/umi": "^1.5.1", + "@solana/web3.js": "^1.72.0" + } + }, + "node_modules/@metaplex-foundation/umi-downloader-http": { + "version": "1.5.1", + "license": "MIT", + "peerDependencies": { + "@metaplex-foundation/umi": "^1.5.1" + } + }, + "node_modules/@metaplex-foundation/umi-eddsa-web3js": { + "version": "1.5.1", + "license": "MIT", + "dependencies": { + "@metaplex-foundation/umi-web3js-adapters": "^1.5.1", + "@noble/curves": "^1.0.0", + "yaml": "^2.7.0" + }, + "peerDependencies": { + "@metaplex-foundation/umi": "^1.5.1", + "@solana/web3.js": "^1.72.0" + } + }, + "node_modules/@metaplex-foundation/umi-http-fetch": { + "version": "1.5.1", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.7" + }, + "peerDependencies": { + "@metaplex-foundation/umi": "^1.5.1" + } + }, + "node_modules/@metaplex-foundation/umi-options": { + "version": "1.5.1", + "license": "MIT" + }, + "node_modules/@metaplex-foundation/umi-program-repository": { + "version": "1.5.1", + "license": "MIT", + "peerDependencies": { + "@metaplex-foundation/umi": "^1.5.1" + } + }, + "node_modules/@metaplex-foundation/umi-public-keys": { + "version": "1.5.1", + "license": "MIT", + "dependencies": { + "@metaplex-foundation/umi-serializers-encodings": "^1.5.1" + } + }, + "node_modules/@metaplex-foundation/umi-rpc-chunk-get-accounts": { + "version": "1.5.1", + "license": "MIT", + "peerDependencies": { + "@metaplex-foundation/umi": "^1.5.1" + } + }, + "node_modules/@metaplex-foundation/umi-rpc-web3js": { + "version": "1.5.1", + "license": "MIT", + "dependencies": { + "@metaplex-foundation/umi-web3js-adapters": "^1.5.1" + }, + "peerDependencies": { + "@metaplex-foundation/umi": "^1.5.1", + "@solana/web3.js": "^1.72.0" + } + }, + "node_modules/@metaplex-foundation/umi-serializer-data-view": { + "version": "1.5.1", + "license": "MIT", + "peerDependencies": { + "@metaplex-foundation/umi": "^1.5.1" + } + }, + "node_modules/@metaplex-foundation/umi-serializers": { + "version": "1.5.1", "license": "MIT", "dependencies": { - "@types/mdx": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@metaplex-foundation/umi-options": "^1.5.1", + "@metaplex-foundation/umi-public-keys": "^1.5.1", + "@metaplex-foundation/umi-serializers-core": "^1.5.1", + "@metaplex-foundation/umi-serializers-encodings": "^1.5.1", + "@metaplex-foundation/umi-serializers-numbers": "^1.5.1" + } + }, + "node_modules/@metaplex-foundation/umi-serializers-core": { + "version": "1.5.1", + "license": "MIT" + }, + "node_modules/@metaplex-foundation/umi-serializers-encodings": { + "version": "1.5.1", + "license": "MIT", + "dependencies": { + "@metaplex-foundation/umi-serializers-core": "^1.5.1" + } + }, + "node_modules/@metaplex-foundation/umi-serializers-numbers": { + "version": "1.5.1", + "license": "MIT", + "dependencies": { + "@metaplex-foundation/umi-serializers-core": "^1.5.1" + } + }, + "node_modules/@metaplex-foundation/umi-transaction-factory-web3js": { + "version": "1.5.1", + "license": "MIT", + "dependencies": { + "@metaplex-foundation/umi-web3js-adapters": "^1.5.1" }, "peerDependencies": { - "@types/react": ">=16", - "react": ">=16" + "@metaplex-foundation/umi": "^1.5.1", + "@solana/web3.js": "^1.72.0" } }, - "node_modules/@mermaid-js/parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.2.1.tgz", - "integrity": "sha512-n12NohV3mrUyUL2o93IgG/ifeW9FTyeJn3zDxkhwa8MJ9Fxg3HQMlA3RiGmD/3UnJvheztkjjQAjA2T4LmUcpw==", + "node_modules/@metaplex-foundation/umi-web3js-adapters": { + "version": "1.5.1", "license": "MIT", "dependencies": { - "@chevrotain/types": "~11.1.2" + "buffer": "^6.0.3" + }, + "peerDependencies": { + "@metaplex-foundation/umi": "^1.5.1", + "@solana/web3.js": "^1.72.0" } }, "node_modules/@mysten/bcs": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@mysten/bcs/-/bcs-2.1.1.tgz", - "integrity": "sha512-IDYomoQ6+yvOIClBHmNdScttv+h/HbGGHLEdUmGQptR3YdqqLM5vJD28wf2QP/UXCEBkCoG7TOT6pFNw8ExYmA==", "license": "Apache-2.0", "dependencies": { "@mysten/utils": "^0.4.1", @@ -7380,9 +7554,7 @@ } }, "node_modules/@mysten/sui": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/@mysten/sui/-/sui-2.29.0.tgz", - "integrity": "sha512-k7q22+AFQ5SZXOH+a28M1J8iFVbMcWro9mt0Bb7GI1HZNsxJIyQT5Q3iZdrAM0hTwM4XQvzm5Y0f32f0nfiGxw==", + "version": "2.26.2", "license": "Apache-2.0", "dependencies": { "@graphql-typed-document-node/core": "^3.2.0", @@ -7406,12 +7578,10 @@ } }, "node_modules/@mysten/sui/node_modules/@noble/curves": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.4.0.tgz", - "integrity": "sha512-P4/62zrgfH33CneE3Dn4WhJVA22YUU0eR51wKIan4NVRvwsA0YnPTwWGpNbpuacSujmSFLvyzpyuR30+fbq2Ew==", + "version": "2.3.0", "license": "MIT", "dependencies": { - "@noble/hashes": "2.4.0" + "@noble/hashes": "2.3.0" }, "engines": { "node": ">= 20.19.0" @@ -7421,26 +7591,22 @@ } }, "node_modules/@mysten/sui/node_modules/@scure/bip32": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-2.4.0.tgz", - "integrity": "sha512-i3DS0CptAocyvqE4n3SUkpzeQK4vJMFwWLofTwRiiKo2aWojBOfyMCgfKw9HVpO6fSY5AK86sHS/Uzn8kK9Few==", + "version": "2.3.0", "license": "MIT", "dependencies": { - "@noble/curves": "2.4.0", - "@noble/hashes": "2.4.0", - "@scure/base": "2.4.0" + "@noble/curves": "2.3.0", + "@noble/hashes": "2.3.0", + "@scure/base": "2.3.0" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@mysten/sui/node_modules/@scure/bip39": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-2.4.0.tgz", - "integrity": "sha512-82dxFbZUYboyOf0AXiydsQrFQ5Q4h9mX+O2UkE91ROYmsc0BKMGZLwDmy96Jpa2+vrtoxomjUhy1RPIgH/r2nA==", + "version": "2.3.0", "license": "MIT", "dependencies": { - "@noble/hashes": "2.4.0" + "@noble/hashes": "2.3.0" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -7448,29 +7614,13 @@ }, "node_modules/@mysten/utils": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@mysten/utils/-/utils-0.4.1.tgz", - "integrity": "sha512-gffLJ84CbqG/CcOPuVlwXIRu37tFn0BtUx7CQtbLwJzWjUE7FU2Gq8BXWjzxImnvufcyLTaNWtCPG6wq22Zhog==", "license": "Apache-2.0", "dependencies": { "@scure/base": "^2.3.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } - }, "node_modules/@noble/ciphers": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", - "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "dev": true, "license": "MIT", "engines": { @@ -7482,8 +7632,6 @@ }, "node_modules/@noble/curves": { "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", - "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", "license": "MIT", "dependencies": { "@noble/hashes": "1.8.0" @@ -7497,8 +7645,6 @@ }, "node_modules/@noble/curves/node_modules/@noble/hashes": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -7508,9 +7654,7 @@ } }, "node_modules/@noble/hashes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", - "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "version": "2.3.0", "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -7521,8 +7665,6 @@ }, "node_modules/@node-rs/jieba": { "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@node-rs/jieba/-/jieba-1.10.4.tgz", - "integrity": "sha512-GvDgi8MnBiyWd6tksojej8anIx18244NmIOc1ovEw8WKNUejcccLfyu8vj66LWSuoZuKILVtNsOy4jvg3aoxIw==", "license": "MIT", "engines": { "node": ">= 10" @@ -7548,42 +7690,8 @@ "@node-rs/jieba-win32-x64-msvc": "1.10.4" } }, - "node_modules/@node-rs/jieba-android-arm-eabi": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@node-rs/jieba-android-arm-eabi/-/jieba-android-arm-eabi-1.10.4.tgz", - "integrity": "sha512-MhyvW5N3Fwcp385d0rxbCWH42kqDBatQTyP8XbnYbju2+0BO/eTeCCLYj7Agws4pwxn2LtdldXRSKavT7WdzNA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@node-rs/jieba-android-arm64": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@node-rs/jieba-android-arm64/-/jieba-android-arm64-1.10.4.tgz", - "integrity": "sha512-XyDwq5+rQ+Tk55A+FGi6PtJbzf974oqnpyCcCPzwU3QVXJCa2Rr4Lci+fx8oOpU4plT3GuD+chXMYLsXipMgJA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@node-rs/jieba-darwin-arm64": { "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@node-rs/jieba-darwin-arm64/-/jieba-darwin-arm64-1.10.4.tgz", - "integrity": "sha512-G++RYEJ2jo0rxF9626KUy90wp06TRUjAsvY/BrIzEOX/ingQYV/HjwQzNPRR1P1o32a6/U8RGo7zEBhfdybL6w==", "cpu": [ "arm64" ], @@ -7596,228 +7704,94 @@ "node": ">= 10" } }, - "node_modules/@node-rs/jieba-darwin-x64": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@node-rs/jieba-darwin-x64/-/jieba-darwin-x64-1.10.4.tgz", - "integrity": "sha512-MmDNeOb2TXIZCPyWCi2upQnZpPjAxw5ZGEj6R8kNsPXVFALHIKMa6ZZ15LCOkSTsKXVC17j2t4h+hSuyYb6qfQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@node-rs/jieba-freebsd-x64": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@node-rs/jieba-freebsd-x64/-/jieba-freebsd-x64-1.10.4.tgz", - "integrity": "sha512-/x7aVQ8nqUWhpXU92RZqd333cq639i/olNpd9Z5hdlyyV5/B65LLy+Je2B2bfs62PVVm5QXRpeBcZqaHelp/bg==", - "cpu": [ - "x64" - ], + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, "engines": { - "node": ">= 10" + "node": ">= 8" } }, - "node_modules/@node-rs/jieba-linux-arm-gnueabihf": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@node-rs/jieba-linux-arm-gnueabihf/-/jieba-linux-arm-gnueabihf-1.10.4.tgz", - "integrity": "sha512-crd2M35oJBRLkoESs0O6QO3BBbhpv+tqXuKsqhIG94B1d02RVxtRIvSDwO33QurxqSdvN9IeSnVpHbDGkuXm3g==", - "cpu": [ - "arm" - ], + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">= 10" + "node": ">= 8" } }, - "node_modules/@node-rs/jieba-linux-arm64-gnu": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@node-rs/jieba-linux-arm64-gnu/-/jieba-linux-arm64-gnu-1.10.4.tgz", - "integrity": "sha512-omIzNX1psUzPcsdnUhGU6oHeOaTCuCjUgOA/v/DGkvWC1jLcnfXe4vdYbtXMh4XOCuIgS1UCcvZEc8vQLXFbXQ==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, "engines": { - "node": ">= 10" + "node": ">= 8" } }, - "node_modules/@node-rs/jieba-linux-arm64-musl": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@node-rs/jieba-linux-arm64-musl/-/jieba-linux-arm64-musl-1.10.4.tgz", - "integrity": "sha512-Y/tiJ1+HeS5nnmLbZOE+66LbsPOHZ/PUckAYVeLlQfpygLEpLYdlh0aPpS5uiaWMjAXYZYdFkpZHhxDmSLpwpw==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" + "node_modules/@offchainlabs/upgrade-executor": { + "version": "1.1.0-beta.0", + "dev": true, + "license": "Apache 2.0", + "dependencies": { + "@openzeppelin/contracts": "4.7.3", + "@openzeppelin/contracts-upgradeable": "4.7.3" } }, - "node_modules/@node-rs/jieba-linux-x64-gnu": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@node-rs/jieba-linux-x64-gnu/-/jieba-linux-x64-gnu-1.10.4.tgz", - "integrity": "sha512-WZO8ykRJpWGE9MHuZpy1lu3nJluPoeB+fIJJn5CWZ9YTVhNDWoCF4i/7nxz1ntulINYGQ8VVuCU9LD86Mek97g==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } + "node_modules/@offchainlabs/upgrade-executor/node_modules/@openzeppelin/contracts-upgradeable": { + "version": "4.7.3", + "dev": true, + "license": "MIT" }, - "node_modules/@node-rs/jieba-linux-x64-musl": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@node-rs/jieba-linux-x64-musl/-/jieba-linux-x64-musl-1.10.4.tgz", - "integrity": "sha512-uBBD4S1rGKcgCyAk6VCKatEVQb6EDD5I40v/DxODi5CuZVCANi9m5oee/MQbAoaX7RydA2f0OSCE9/tcwXEwUg==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } + "node_modules/@openzeppelin/contracts": { + "version": "4.7.3", + "dev": true, + "license": "MIT" }, - "node_modules/@node-rs/jieba-wasm32-wasi": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@node-rs/jieba-wasm32-wasi/-/jieba-wasm32-wasi-1.10.4.tgz", - "integrity": "sha512-Y2umiKHjuIJy0uulNDz9SDYHdfq5Hmy7jY5nORO99B4pySKkcrMjpeVrmWXJLIsEKLJwcCXHxz8tjwU5/uhz0A==", - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.3" - }, - "engines": { - "node": ">=14.0.0" - } + "node_modules/@openzeppelin/contracts-4.7.3": { + "name": "@openzeppelin/contracts", + "version": "4.7.3", + "dev": true, + "license": "MIT" }, - "node_modules/@node-rs/jieba-win32-arm64-msvc": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@node-rs/jieba-win32-arm64-msvc/-/jieba-win32-arm64-msvc-1.10.4.tgz", - "integrity": "sha512-nwMtViFm4hjqhz1it/juQnxpXgqlGltCuWJ02bw70YUDMDlbyTy3grCJPpQQpueeETcALUnTxda8pZuVrLRcBA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } + "node_modules/@openzeppelin/contracts-4.8.3": { + "name": "@openzeppelin/contracts", + "version": "4.8.3", + "dev": true, + "license": "MIT" }, - "node_modules/@node-rs/jieba-win32-ia32-msvc": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@node-rs/jieba-win32-ia32-msvc/-/jieba-win32-ia32-msvc-1.10.4.tgz", - "integrity": "sha512-DCAvLx7Z+W4z5oKS+7vUowAJr0uw9JBw8x1Y23Xs/xMA4Em+OOSiaF5/tCJqZUCJ8uC4QeImmgDFiBqGNwxlyA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } + "node_modules/@openzeppelin/contracts-4.9.6": { + "name": "@openzeppelin/contracts", + "version": "4.9.6", + "dev": true, + "license": "MIT" }, - "node_modules/@node-rs/jieba-win32-x64-msvc": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@node-rs/jieba-win32-x64-msvc/-/jieba-win32-x64-msvc-1.10.4.tgz", - "integrity": "sha512-+sqemSfS1jjb+Tt7InNbNzrRh1Ua3vProVvC4BZRPg010/leCbGFFiQHpzcPRfpxAXZrzG5Y0YBTsPzN/I4yHQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } + "node_modules/@openzeppelin/contracts-5.0.2": { + "name": "@openzeppelin/contracts", + "version": "5.0.2", + "dev": true, + "license": "MIT" }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } + "node_modules/@openzeppelin/contracts-5.1.0": { + "name": "@openzeppelin/contracts", + "version": "5.1.0", + "dev": true, + "license": "MIT" }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } + "node_modules/@openzeppelin/contracts-5.3.0": { + "name": "@openzeppelin/contracts", + "version": "5.3.0", + "dev": true, + "license": "MIT" }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } + "node_modules/@openzeppelin/contracts-upgradeable": { + "version": "4.9.6", + "dev": true, + "license": "MIT" }, "node_modules/@oxfmt/binding-android-arm-eabi": { "version": "0.66.0", @@ -7946,9 +7920,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7966,9 +7937,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7986,9 +7954,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -8006,9 +7971,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -8026,9 +7988,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -8046,9 +8005,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -8066,9 +8022,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -8086,9 +8039,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -8168,8 +8118,6 @@ }, "node_modules/@oxlint-tsgolint/darwin-arm64": { "version": "7.0.2001", - "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-7.0.2001.tgz", - "integrity": "sha512-CUJEdbSZ54+Xy9OXqOhWLTKZKV0BBiV7C2i/ygyVmXtkUNXx5YCzN8DpSSshTAKktoL7S+tnQ/ftFG/i7X896w==", "cpu": [ "arm64" ], @@ -8251,9 +8199,9 @@ ] }, "node_modules/@oxlint/binding-android-arm-eabi": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.81.0.tgz", - "integrity": "sha512-IcCRsXiedJoJopY6mpZUBEeVFsUrutmrG7dZ87zMuKJlhg70Ora9bBl1WcCxZQtyI10YpnVdEso5oCg7YcfSHw==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.82.0.tgz", + "integrity": "sha512-a3LB+C5Dsj5b/qtmG/mv5WrzuiXEpg1KF5nXWcEvaoN5TYAqkIvxPOwTPp3Jy/FoGpRo8zsTFhMElMXfeoOEzA==", "cpu": [ "arm" ], @@ -8268,9 +8216,9 @@ } }, "node_modules/@oxlint/binding-android-arm64": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.81.0.tgz", - "integrity": "sha512-GRrIPyTGVhx3L3h+0T5xT2A0jFAcdPv4+IfuXpGDLIdl6XeYhgg/zw72A5ILZoUgRqZuM8F1y+V/gfDriXSxzQ==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.82.0.tgz", + "integrity": "sha512-OBlhRgNqFblGpGenno/aqOfJLOkQ2B8Ig3iDAalfn0H8hJGZKXPeexCRTDm6uwv6YUjSA9Xnwt1y/Bgj5ZH8uw==", "cpu": [ "arm64" ], @@ -8285,9 +8233,9 @@ } }, "node_modules/@oxlint/binding-darwin-arm64": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.81.0.tgz", - "integrity": "sha512-qNQ9tXRgLuKbqSV1S2h9h4KPHjbovO7RRR2/enUOtHzTkFZ7B9X5zqqHJua8dRyc7dBy7Aoyq5pqTSLFVcAzGQ==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.82.0.tgz", + "integrity": "sha512-dsopxqtY5ZdyT9uLHyGt1SyiLop6hi7hWI3PKpePodkRQOkLaCm+OE4fR9CAz9qdfjiFO8531tX/QDyP/psjFg==", "cpu": [ "arm64" ], @@ -8302,9 +8250,9 @@ } }, "node_modules/@oxlint/binding-darwin-x64": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.81.0.tgz", - "integrity": "sha512-q0QTm32jWga2Gv4j7IaVZN0jYMi9UV73sWVgFtDA4iIfqwMCLLZ3ve+9KwfYtsaKZSgQhmPaogeZWqDZpcY1Pw==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.82.0.tgz", + "integrity": "sha512-94Lu0SgTClKColU66g1VDuigV3HkcbkJBnTtZjGYfE8UPugaWDgKrm2icjC6HJVUYler2OXaHP/X0TBy8+CowQ==", "cpu": [ "x64" ], @@ -8319,9 +8267,9 @@ } }, "node_modules/@oxlint/binding-freebsd-x64": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.81.0.tgz", - "integrity": "sha512-/+8wVWDXEC7wHVAhOc59Fw/SkMc1arLkFD8iQCaSsmzenK1X4doFqquL9H1wrtGUzaiycVqkf/sSpcILK6W1UA==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.82.0.tgz", + "integrity": "sha512-hne/V06ewhh1i0w8+l7GDNROAGCGPmyFuOwiP7YTRu0JycyStJ4785dmF8xU5p0uUwt2emvIF9vc7Xjis+cJ0g==", "cpu": [ "x64" ], @@ -8336,9 +8284,9 @@ } }, "node_modules/@oxlint/binding-linux-arm-gnueabihf": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.81.0.tgz", - "integrity": "sha512-4xt422FEgioRq9hAL4Tq7fujGUWnc8z1BJ+Oi8RN8vB8axaP+sdK6a2xdlcQCCYnJg9QMuMFS0AucuIFx/EacA==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.82.0.tgz", + "integrity": "sha512-aWY2xtbZf1LneW9Qsv/n2Sp8gOu74JrlQzEtj4coHX2SHFrCfhmAumaU+sI/A5nr+yoTRTSmI/pL2s6ADlNSkw==", "cpu": [ "arm" ], @@ -8353,9 +8301,9 @@ } }, "node_modules/@oxlint/binding-linux-arm-musleabihf": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.81.0.tgz", - "integrity": "sha512-u3vna8KdGplH4DRCW9K54D68fcMo7IxVrkCJWwXnIhwtBdnDnYrmzOUA/XjmBlPpcLsgw9Z5BNdY4za9+Dj+MQ==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.82.0.tgz", + "integrity": "sha512-Fe+TtXCXMh/5f7kWlZ2VAwsMumZWtraFlKVk1NJlL52/beGwfDE7ov+/8gVirHzWokzGu7X65hSPq0ucPDskWQ==", "cpu": [ "arm" ], @@ -8370,16 +8318,13 @@ } }, "node_modules/@oxlint/binding-linux-arm64-gnu": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.81.0.tgz", - "integrity": "sha512-3j9k+gsYsE7nv71GWotXsqsa2l9/aJenD7dVHNt/CBvsb0SgRjSMnHFeP59IXUAl1wvVFhqGl2wJNMwWU3UBlA==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.82.0.tgz", + "integrity": "sha512-6azCZ6OJudlvipNttXCCQcyeFfcJ/NvUZdSN1z8elo73kCHtyQC7WTiUcSjWYvJ1jaq9KDUyMAoAS/vNzhBomA==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -8390,16 +8335,13 @@ } }, "node_modules/@oxlint/binding-linux-arm64-musl": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.81.0.tgz", - "integrity": "sha512-k5iAp3dNxW0/uDCBY+WSm8jKB2szu7SkEQZdgRRpDXvuDd69vvDcqhB3A/pWCfCwXyenjNjFn9Td1fVoyAc+Yg==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.82.0.tgz", + "integrity": "sha512-PLEaSD8IAIIlwW4dwOd9YaxuxeOpwiXL4J24rcnE4iNtyM5j9Q9/3+gti08oXpx0u2ygNjRDx9xjWWpQonuJEw==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -8410,16 +8352,13 @@ } }, "node_modules/@oxlint/binding-linux-ppc64-gnu": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.81.0.tgz", - "integrity": "sha512-TFqLja3uYmVSte6nof9GWrex9Z8WgdZrNiLC6Te5rXGDqXB2y4j/26iFhwosXiAFqDhE9JJVuuCkDKLwptTn1g==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.82.0.tgz", + "integrity": "sha512-D94em/BwknNTn4vqxjHh5wb2oL566eFhArabqKIr0cNZMHOJuiraFp1A8tXpH05bbE5tqwEfLXTI0MWEGtn3Dw==", "cpu": [ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -8430,16 +8369,13 @@ } }, "node_modules/@oxlint/binding-linux-riscv64-gnu": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.81.0.tgz", - "integrity": "sha512-UEcySvGS0NOVo7h7n7CYyJL9+6gFAh7Zc/ToDXVScFvzHSTIxtzkMVU30rmQ6+nQ1LF+UdiRDdJajpDu+OylLg==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.82.0.tgz", + "integrity": "sha512-MOprxBaoYU2D4VgxXCl3ghydThWtx7Um1lL51kGYNeQ5Al7WzsH7/tqGdNtbLrIWnjq3bsm13+nz/gRIxjrOXw==", "cpu": [ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -8450,16 +8386,13 @@ } }, "node_modules/@oxlint/binding-linux-riscv64-musl": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.81.0.tgz", - "integrity": "sha512-H+diDbhD00+wI1IRP8Kz88x/lat+DgtoBJzoTthS16xkTJGNaEkfb8gzmd1rzc/2uDQQMl7GNl+JFUacVeWxIA==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.82.0.tgz", + "integrity": "sha512-5h55QsfJ/luDXZzC20k6SNOY1Az+dCP9WvntKtcUWh2JhckAdwApY2ZusaBTwLENnReXU+A2fJtSrYvZJNKNPg==", "cpu": [ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -8470,16 +8403,13 @@ } }, "node_modules/@oxlint/binding-linux-s390x-gnu": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.81.0.tgz", - "integrity": "sha512-8znJ/5TekjOKg1j1Acho4PJMdiAHLtlcXuWEiipOhAMV6rQcXdmDdXCbheyDczN6TjBwiNfjcP81k4AthrKRzw==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.82.0.tgz", + "integrity": "sha512-IE8NJNLlHr0CaXyGJPGVn0eTkUyoj1I2UfA8x7I4PSOYKsQ/6btVC7Pywrj5onk0cMH25r6Z38SoN3AvE5Zuog==", "cpu": [ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -8490,16 +8420,13 @@ } }, "node_modules/@oxlint/binding-linux-x64-gnu": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.81.0.tgz", - "integrity": "sha512-Q2Wj70yFsvn5QjlmifFzbj4H+kJy53bwqc41o1fzoM7MpLV1NIbhg/LpWXRfC6KOkSAdUx1Wd8VJsdPmhp/HRA==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.82.0.tgz", + "integrity": "sha512-XUUUxaBo9XKl+J1B9EmP1cTGQPddzeURvoGkfwh/94PGnbW+hBprDljneoI2M1jzC1bzrIV3ihc7iM9UXl8+tg==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -8510,16 +8437,13 @@ } }, "node_modules/@oxlint/binding-linux-x64-musl": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.81.0.tgz", - "integrity": "sha512-cPInHp/ddEe5qkyK2IiyQ8Q3Mp2oLLEhhsGgTK2oZx4L6+llGam1H1yBvJZ7qHfOXj8N3hxBS8sj4tO+gtFlIg==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.82.0.tgz", + "integrity": "sha512-SWLSFulX9TDuH6yvbPYp4+VNn6jkkIvvI+KiujDM5rWBRHEfkesCC/pCneIIUr6ovkxZ5fRtpi2v5Cz5FrMJZg==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -8530,9 +8454,9 @@ } }, "node_modules/@oxlint/binding-openharmony-arm64": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.81.0.tgz", - "integrity": "sha512-0CQxSX4ajqm07AHBf5U33qQzXKdd7wtq/oTL/7vpY6RNNuxrRi8W4bqUV1Jyu/vj+9KmxQyDhxfeVX1nQL6kfg==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.82.0.tgz", + "integrity": "sha512-BQy35f6ZUdNr9a6c7B7orxQTcLjByGT2z3WAgmRovpRwmPYAaJ+NTplmMzhdjdJ4qSchfMNZy/Ukg+qRg6zseQ==", "cpu": [ "arm64" ], @@ -8547,340 +8471,98 @@ } }, "node_modules/@oxlint/binding-win32-arm64-msvc": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.81.0.tgz", - "integrity": "sha512-l0hbeISm9673hVrrQU8j/p2M7YH9Ouoj7p7E/QM55NTrKVLP+P3PF8hLu+OY+x0VtGRW+ggiQKZqmdYps9H+TA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-win32-ia32-msvc": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.81.0.tgz", - "integrity": "sha512-ksqPP5jbFXcYreEQ7zdJh06rJQBymCTyGRCdaXjfcf2aG4f8KxUWY5wcgYHmaTK+FJ4bPG5sUAdOX+6trnH1JA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxlint/binding-win32-x64-msvc": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.81.0.tgz", - "integrity": "sha512-IZuUCwGw9emG5JtCp+fYGB+Z4OWEoeEcM8R5BA1pYw63/ieYFVdcU2ylxTpHbVHSenZnsYE+ZZ20uHAJszQ4cA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@parcel/watcher": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", - "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.3", - "is-glob": "^4.0.3", - "node-addon-api": "^7.0.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.6.0", - "@parcel/watcher-darwin-arm64": "2.6.0", - "@parcel/watcher-darwin-x64": "2.6.0", - "@parcel/watcher-freebsd-x64": "2.6.0", - "@parcel/watcher-linux-arm-glibc": "2.6.0", - "@parcel/watcher-linux-arm-musl": "2.6.0", - "@parcel/watcher-linux-arm64-glibc": "2.6.0", - "@parcel/watcher-linux-arm64-musl": "2.6.0", - "@parcel/watcher-linux-x64-glibc": "2.6.0", - "@parcel/watcher-linux-x64-musl": "2.6.0", - "@parcel/watcher-win32-arm64": "2.6.0", - "@parcel/watcher-win32-x64": "2.6.0" - } - }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", - "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", - "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", - "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", - "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", - "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", - "cpu": [ - "arm" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", - "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", - "cpu": [ - "arm" - ], - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", - "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", - "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.82.0.tgz", + "integrity": "sha512-V4QhSTg5gctZue8RJjsGi7NpQPThr/p1/HfmiMC5kfe1KFEup9SQRVub4A6kijQjdHfxj7bLL1KO3QO7/5bwMQ==", "cpu": [ "arm64" ], - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", - "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], + "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", - "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.82.0.tgz", + "integrity": "sha512-TUSCLaKB2yktpFAJ/r3HAUYsaV/3DT7JS4iNKyoh3a9YNwD0UG7Ezh4D8m23654vQcU6P/RQrCAjRPKe4peP/A==", "cpu": [ - "x64" - ], - "libc": [ - "musl" + "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", - "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.82.0.tgz", + "integrity": "sha512-VTVoRIWJTb+wvUX8EYoPArfFH02whuR10goFXE/LHRRX33ajRrFgqbcONXZMiF4C5rnattfkm87HqYn8jb8hmQ==", "cpu": [ - "arm64" + "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" + }, "engines": { "node": ">= 10.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" } }, - "node_modules/@parcel/watcher-win32-x64": { + "node_modules/@parcel/watcher-darwin-arm64": { "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", - "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", "cpu": [ - "x64" + "arm64" ], "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { "node": ">= 10.0.0" @@ -8892,15 +8574,11 @@ }, "node_modules/@parcel/watcher/node_modules/node-addon-api": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "license": "MIT", "optional": true }, "node_modules/@parcel/watcher/node_modules/picomatch": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", - "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "license": "MIT", "optional": true, "engines": { @@ -8912,8 +8590,6 @@ }, "node_modules/@peculiar/asn1-cms": { "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.9.4.tgz", - "integrity": "sha512-cben7oxmQsUGZqotus7yt0srYdncOT6RNWcTQ77T2RFOXejYVYkXadrfePdRcrVpO9K95IRLKKglG2k38jKXuw==", "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.9.4", @@ -8928,8 +8604,6 @@ }, "node_modules/@peculiar/asn1-csr": { "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.9.4.tgz", - "integrity": "sha512-xd4YN4vpRjkDAQWVfZZkeu12IEND7DOpkqaHSIHxZl1uggUNa9Ju0QxY2jHvDAS9pP0zhRBytg8ifsnGo3V0jw==", "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.9.4", @@ -8943,8 +8617,6 @@ }, "node_modules/@peculiar/asn1-ecc": { "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.9.4.tgz", - "integrity": "sha512-JJXefFshRAuVAjWQo/39bkg1ywc1VaiO44S8RRC+Ykvf/u2KDmYffoDb0ZBPCR5uJy4AGKQhl8mX+Q8ShcWaXQ==", "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.9.4", @@ -8958,8 +8630,6 @@ }, "node_modules/@peculiar/asn1-pfx": { "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.9.4.tgz", - "integrity": "sha512-khuGzHTzNzk4GDlIBEILyIs6Lce0yn0ZBdoI9v93kmNncfZRhD+AQ5ODFqdhvoE8cMJF/JMTQ8yA+t1D14kqCw==", "license": "MIT", "dependencies": { "@peculiar/asn1-cms": "^2.9.4", @@ -8975,8 +8645,6 @@ }, "node_modules/@peculiar/asn1-pkcs8": { "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.9.4.tgz", - "integrity": "sha512-duRdotlUx9eDZe6QrQpQKl61RbWykCHBCkKayP8V8XdEFwlKHZ8qGGDMyS6Pye7OX7nLFttTTpRkJeet78ckwQ==", "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.9.4", @@ -8990,8 +8658,6 @@ }, "node_modules/@peculiar/asn1-pkcs9": { "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.9.4.tgz", - "integrity": "sha512-kaL4cNxBpdQE2dKlyZBqz4ygCrwffO+8wfoxTEqM1Z8RadvCeELBRzcv0dzM8aY9azHMwODO5nxU65zXmhToOQ==", "license": "MIT", "dependencies": { "@peculiar/asn1-cms": "^2.9.4", @@ -9009,8 +8675,6 @@ }, "node_modules/@peculiar/asn1-rsa": { "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.9.4.tgz", - "integrity": "sha512-pZ96eD1PptovcWQ/GSmuNFXd/7EQJNlKfDaNCyE2rx3W0v6QFelkzquVqRSRyyDXXCYD69ZXJDzZ8GhIiQzKoA==", "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.9.4", @@ -9024,8 +8688,6 @@ }, "node_modules/@peculiar/asn1-schema": { "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.9.4.tgz", - "integrity": "sha512-GjzePcT9Iw8NzeOPf73iNS9xM+TBhd/FilAfP+RQGkTMQJTVWtytN3JHJACCjf/ABNau5S7mS3g+DcuxmRgYEg==", "license": "MIT", "dependencies": { "@peculiar/utils": "^2.0.2", @@ -9038,8 +8700,6 @@ }, "node_modules/@peculiar/asn1-x509": { "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.9.4.tgz", - "integrity": "sha512-CxhBo/RdEbMMob7T31ZdQjGuoyRFLVwrDzTn25bihzBasRg9kRm/0IxIPvhgQtcK/9dNcO1XQL2fuPugwELL0Q==", "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.9.4", @@ -9053,8 +8713,6 @@ }, "node_modules/@peculiar/asn1-x509-attr": { "version": "2.9.4", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.9.4.tgz", - "integrity": "sha512-ehQXbpQaQYycgu8OrvigwSPTFfVRcu0ECNYCWw+yzBp02Lw5paRqzzhUpfOgO2K38+WfFZuEz/0RPtam5g0OMg==", "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.9.4", @@ -9068,8 +8726,6 @@ }, "node_modules/@peculiar/utils": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", - "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", "license": "MIT", "dependencies": { "tslib": "^2.8.1" @@ -9077,8 +8733,6 @@ }, "node_modules/@peculiar/x509": { "version": "1.14.3", - "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", - "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", "license": "MIT", "dependencies": { "@peculiar/asn1-cms": "^2.6.0", @@ -9099,8 +8753,6 @@ }, "node_modules/@pnpm/config.env-replace": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", "license": "MIT", "engines": { "node": ">=12.22.0" @@ -9108,8 +8760,6 @@ }, "node_modules/@pnpm/network.ca-file": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", "license": "MIT", "dependencies": { "graceful-fs": "4.2.10" @@ -9120,14 +8770,10 @@ }, "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "license": "ISC" }, "node_modules/@pnpm/npm-conf": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.3.tgz", - "integrity": "sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==", "license": "MIT", "dependencies": { "@pnpm/config.env-replace": "^1.1.0", @@ -9140,14 +8786,10 @@ }, "node_modules/@polka/url": { "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", "license": "MIT" }, "node_modules/@protobuf-ts/grpcweb-transport": { "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@protobuf-ts/grpcweb-transport/-/grpcweb-transport-2.11.1.tgz", - "integrity": "sha512-1W4utDdvOB+RHMFQ0soL4JdnxjXV+ddeGIUg08DvZrA8Ms6k5NN6GBFU2oHZdTOcJVpPrDJ02RJlqtaoCMNBtw==", "license": "Apache-2.0", "dependencies": { "@protobuf-ts/runtime": "^2.11.1", @@ -9156,14 +8798,10 @@ }, "node_modules/@protobuf-ts/runtime": { "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.11.1.tgz", - "integrity": "sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ==", "license": "(Apache-2.0 AND BSD-3-Clause)" }, "node_modules/@protobuf-ts/runtime-rpc": { "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.11.1.tgz", - "integrity": "sha512-4CqqUmNA+/uMz00+d3CYKgElXO9VrEbucjnBFEjqI4GuDrEQ32MaI3q+9qPBvIGOlL4PmHXrzM32vBPWRhQKWQ==", "license": "Apache-2.0", "dependencies": { "@protobuf-ts/runtime": "^2.11.1" @@ -9171,8 +8809,6 @@ }, "node_modules/@redocly/ajv": { "version": "8.18.3", - "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.18.3.tgz", - "integrity": "sha512-l42u0of3hY98sN2A+M4qTX1O/KrpgGH32Hu9kP2GtHyD5Dfqq86PKFLe5dwaD8DEnNmlOlll2BAmeEtf0DaySg==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -9186,22 +8822,18 @@ } }, "node_modules/@redocly/config": { - "version": "0.55.0", - "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.55.0.tgz", - "integrity": "sha512-PlaCpehzQoe6R9YksDI5gW4mfTA2UfnpCGldXXs7/axeyPdAK/T2UpfsSD9sOedKaq6VF9jtE9qXmnH71CAclg==", + "version": "0.53.1", "license": "MIT", "dependencies": { "json-schema-to-ts": "2.7.2" } }, "node_modules/@redocly/openapi-core": { - "version": "2.51.1", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-2.51.1.tgz", - "integrity": "sha512-zYuXCK+WDYXy3FzjLF7pPTtAsMdSP77kPb0VFY4ZcysRrpWywYrwAzjvxAfLkdqO5Ab1ZTZpHCb/COEjuiYcmg==", + "version": "2.47.0", "license": "MIT", "dependencies": { "@redocly/ajv": "^8.18.3", - "@redocly/config": "^0.55.0", + "@redocly/config": "^0.53.1", "ajv": "npm:@redocly/ajv@^8.18.3", "ajv-formats": "^3.0.1", "colorette": "^1.2.0", @@ -9219,8 +8851,6 @@ }, "node_modules/@redocly/openapi-core/node_modules/graphql": { "version": "16.14.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", - "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" @@ -9228,8 +8858,6 @@ }, "node_modules/@redocly/openapi-core/node_modules/js-yaml": { "version": "5.2.3", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.3.tgz", - "integrity": "sha512-n+mUVyUX5bVv7G/G2zyIHOhdxfuU1dY2NOFzTQUWiMUbFss8b57NFlgCCaggU78wSw5KVS9cllzeLyzyR+n5nw==", "funding": [ { "type": "github", @@ -9250,8 +8878,6 @@ }, "node_modules/@redocly/openapi-core/node_modules/picomatch": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", - "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "license": "MIT", "engines": { "node": ">=12" @@ -9262,8 +8888,6 @@ }, "node_modules/@reduxjs/toolkit": { "version": "2.12.0", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", - "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -9286,10 +8910,13 @@ } } }, + "node_modules/@scroll-tech/contracts": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, "node_modules/@scure/base": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.4.0.tgz", - "integrity": "sha512-thZ1TuJwFwBblOhgsjDKvvGirBxNp+wSvY/DR6tJBJOTDhdAAcHJ8Vbr2eFnqaxeca4+t0i9KBf+uHYGWwZORg==", + "version": "2.3.0", "license": "MIT", "funding": { "url": "https://paulmillr.com/funding/" @@ -9297,8 +8924,6 @@ }, "node_modules/@scure/bip32": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", - "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", "dev": true, "license": "MIT", "dependencies": { @@ -9312,8 +8937,6 @@ }, "node_modules/@scure/bip32/node_modules/@noble/hashes": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, "license": "MIT", "engines": { @@ -9325,8 +8948,6 @@ }, "node_modules/@scure/bip32/node_modules/@scure/base": { "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", - "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", "dev": true, "license": "MIT", "funding": { @@ -9335,8 +8956,6 @@ }, "node_modules/@scure/bip39": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", - "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", "dev": true, "license": "MIT", "dependencies": { @@ -9349,8 +8968,6 @@ }, "node_modules/@scure/bip39/node_modules/@noble/hashes": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, "license": "MIT", "engines": { @@ -9362,8 +8979,6 @@ }, "node_modules/@scure/bip39/node_modules/@scure/base": { "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", - "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", "dev": true, "license": "MIT", "funding": { @@ -9372,14 +8987,10 @@ }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", "license": "MIT" }, "node_modules/@shikijs/engine-oniguruma": { "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", - "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", "dev": true, "license": "MIT", "dependencies": { @@ -9389,8 +9000,6 @@ }, "node_modules/@shikijs/langs": { "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", - "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", "dev": true, "license": "MIT", "dependencies": { @@ -9399,8 +9008,6 @@ }, "node_modules/@shikijs/themes": { "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", - "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", "dev": true, "license": "MIT", "dependencies": { @@ -9409,8 +9016,6 @@ }, "node_modules/@shikijs/types": { "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", - "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9420,21 +9025,15 @@ }, "node_modules/@shikijs/vscode-textmate": { "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", - "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "dev": true, "license": "MIT" }, "node_modules/@sinclair/typebox": { "version": "0.27.12", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", - "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", "license": "MIT" }, "node_modules/@sindresorhus/is": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "license": "MIT", "engines": { "node": ">=10" @@ -9445,8 +9044,6 @@ }, "node_modules/@sindresorhus/merge-streams": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, "license": "MIT", "engines": { @@ -9458,8 +9055,6 @@ }, "node_modules/@slorber/remark-comment": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", - "integrity": "sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==", "license": "MIT", "dependencies": { "micromark-factory-space": "^1.0.0", @@ -9469,8 +9064,6 @@ }, "node_modules/@solana/buffer-layout": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz", - "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==", "license": "MIT", "dependencies": { "buffer": "~6.0.3" @@ -9481,8 +9074,6 @@ }, "node_modules/@solana/buffer-layout-utils": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@solana/buffer-layout-utils/-/buffer-layout-utils-0.3.0.tgz", - "integrity": "sha512-MuQOCC1j0np1xH9yAv0ZWWfwvr7Bt7Sz4LId11Wi4wDdAmJ+lobE+vHg/mZmGcihF0BIkqVBNxGmlv8QE5DrtA==", "license": "Apache-2.0", "dependencies": { "@solana/buffer-layout": "^4.0.0", @@ -9496,8 +9087,6 @@ }, "node_modules/@solana/codecs": { "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-2.0.0-rc.1.tgz", - "integrity": "sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ==", "license": "MIT", "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", @@ -9512,8 +9101,6 @@ }, "node_modules/@solana/codecs-core": { "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.0.0-rc.1.tgz", - "integrity": "sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ==", "license": "MIT", "dependencies": { "@solana/errors": "2.0.0-rc.1" @@ -9524,8 +9111,6 @@ }, "node_modules/@solana/codecs-data-structures": { "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-rc.1.tgz", - "integrity": "sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog==", "license": "MIT", "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", @@ -9538,8 +9123,6 @@ }, "node_modules/@solana/codecs-numbers": { "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.0.0-rc.1.tgz", - "integrity": "sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ==", "license": "MIT", "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", @@ -9551,8 +9134,6 @@ }, "node_modules/@solana/codecs-strings": { "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-2.0.0-rc.1.tgz", - "integrity": "sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g==", "license": "MIT", "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", @@ -9566,8 +9147,6 @@ }, "node_modules/@solana/errors": { "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.0.0-rc.1.tgz", - "integrity": "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ==", "license": "MIT", "dependencies": { "chalk": "^5.3.0", @@ -9582,8 +9161,6 @@ }, "node_modules/@solana/errors/node_modules/chalk": { "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -9594,8 +9171,6 @@ }, "node_modules/@solana/options": { "version": "2.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@solana/options/-/options-2.0.0-rc.1.tgz", - "integrity": "sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA==", "license": "MIT", "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", @@ -9610,8 +9185,6 @@ }, "node_modules/@solana/spl-token": { "version": "0.4.15", - "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.4.15.tgz", - "integrity": "sha512-3Lof3mNov8NVQ3PalIWb1Jgr/TZ6lYM+/sexv2TLqdhNFVth2OfWmH3d7QucgMjSbokkjNiNlRr6I8Fd269uaw==", "license": "Apache-2.0", "dependencies": { "@solana/buffer-layout": "^4.0.0", @@ -9629,8 +9202,6 @@ }, "node_modules/@solana/spl-token-group": { "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@solana/spl-token-group/-/spl-token-group-0.0.7.tgz", - "integrity": "sha512-V1N/iX7Cr7H0uazWUT2uk27TMqlqedpXHRqqAbVO2gvmJyT0E0ummMEAVQeXZ05ZhQ/xF39DLSdBp90XebWEug==", "license": "Apache-2.0", "dependencies": { "@solana/codecs": "2.0.0-rc.1" @@ -9644,8 +9215,6 @@ }, "node_modules/@solana/spl-token-metadata": { "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@solana/spl-token-metadata/-/spl-token-metadata-0.1.6.tgz", - "integrity": "sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA==", "license": "Apache-2.0", "dependencies": { "@solana/codecs": "2.0.0-rc.1" @@ -9659,9 +9228,8 @@ }, "node_modules/@solana/web3.js": { "version": "1.98.4", - "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz", - "integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.25.0", "@noble/curves": "^1.4.2", @@ -9682,8 +9250,6 @@ }, "node_modules/@solana/web3.js/node_modules/@noble/hashes": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -9694,8 +9260,6 @@ }, "node_modules/@solana/web3.js/node_modules/@solana/codecs-core": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz", - "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==", "license": "MIT", "dependencies": { "@solana/errors": "2.3.0" @@ -9709,8 +9273,6 @@ }, "node_modules/@solana/web3.js/node_modules/@solana/codecs-numbers": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz", - "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==", "license": "MIT", "dependencies": { "@solana/codecs-core": "2.3.0", @@ -9725,8 +9287,6 @@ }, "node_modules/@solana/web3.js/node_modules/@solana/errors": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz", - "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==", "license": "MIT", "dependencies": { "chalk": "^5.4.1", @@ -9744,8 +9304,6 @@ }, "node_modules/@solana/web3.js/node_modules/base-x": { "version": "3.0.11", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", - "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", "license": "MIT", "dependencies": { "safe-buffer": "^5.0.1" @@ -9753,8 +9311,6 @@ }, "node_modules/@solana/web3.js/node_modules/borsh": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", - "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", "license": "Apache-2.0", "dependencies": { "bn.js": "^5.2.0", @@ -9764,8 +9320,6 @@ }, "node_modules/@solana/web3.js/node_modules/bs58": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", "license": "MIT", "dependencies": { "base-x": "^3.0.2" @@ -9773,8 +9327,6 @@ }, "node_modules/@solana/web3.js/node_modules/chalk": { "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -9785,8 +9337,6 @@ }, "node_modules/@solana/web3.js/node_modules/commander": { "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "license": "MIT", "engines": { "node": ">=20" @@ -9794,8 +9344,6 @@ }, "node_modules/@solana/web3.js/node_modules/superstruct": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", - "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -9803,20 +9351,14 @@ }, "node_modules/@standard-schema/spec": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "license": "MIT" }, "node_modules/@standard-schema/utils": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", - "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", "license": "MIT" }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", "license": "MIT", "engines": { "node": ">=14" @@ -9831,8 +9373,6 @@ }, "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", "license": "MIT", "engines": { "node": ">=14" @@ -9847,8 +9387,6 @@ }, "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", - "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", "license": "MIT", "engines": { "node": ">=14" @@ -9863,8 +9401,6 @@ }, "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", - "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", "license": "MIT", "engines": { "node": ">=14" @@ -9879,8 +9415,6 @@ }, "node_modules/@svgr/babel-plugin-svg-dynamic-title": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", - "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", "license": "MIT", "engines": { "node": ">=14" @@ -9895,8 +9429,6 @@ }, "node_modules/@svgr/babel-plugin-svg-em-dimensions": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", - "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", "license": "MIT", "engines": { "node": ">=14" @@ -9911,8 +9443,6 @@ }, "node_modules/@svgr/babel-plugin-transform-react-native-svg": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", - "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", "license": "MIT", "engines": { "node": ">=14" @@ -9927,8 +9457,6 @@ }, "node_modules/@svgr/babel-plugin-transform-svg-component": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", - "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", "license": "MIT", "engines": { "node": ">=12" @@ -9943,8 +9471,6 @@ }, "node_modules/@svgr/babel-preset": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", - "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", "license": "MIT", "dependencies": { "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", @@ -9969,9 +9495,8 @@ }, "node_modules/@svgr/core": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", - "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -9989,8 +9514,6 @@ }, "node_modules/@svgr/hast-util-to-babel-ast": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", - "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", "license": "MIT", "dependencies": { "@babel/types": "^7.21.3", @@ -10006,8 +9529,6 @@ }, "node_modules/@svgr/plugin-jsx": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", - "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", "license": "MIT", "dependencies": { "@babel/core": "^7.21.3", @@ -10028,8 +9549,6 @@ }, "node_modules/@svgr/plugin-svgo": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", - "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", "license": "MIT", "dependencies": { "cosmiconfig": "^8.1.3", @@ -10049,8 +9568,6 @@ }, "node_modules/@svgr/webpack": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", - "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", "license": "MIT", "dependencies": { "@babel/core": "^7.21.3", @@ -10072,8 +9589,6 @@ }, "node_modules/@swc/helpers": { "version": "0.5.23", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", - "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.8.0" @@ -10081,8 +9596,6 @@ }, "node_modules/@tailwindcss/container-queries": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/container-queries/-/container-queries-0.1.1.tgz", - "integrity": "sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==", "license": "MIT", "peerDependencies": { "tailwindcss": ">=3.2.0" @@ -10090,8 +9603,6 @@ }, "node_modules/@ton-community/ton-ledger": { "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@ton-community/ton-ledger/-/ton-ledger-7.3.0.tgz", - "integrity": "sha512-eG4KqQaQoUgdVzedUlt8ZkwHDw7JiXnPqZOO+1fUKISwjU2OdiRIeo+TB0yKK3X3fm/0hgQbFBEZAbGSCKvzTw==", "license": "MIT", "dependencies": { "@ledgerhq/hw-transport": "^6.31.4", @@ -10104,17 +9615,14 @@ }, "node_modules/@ton/core": { "version": "0.63.1", - "resolved": "https://registry.npmjs.org/@ton/core/-/core-0.63.1.tgz", - "integrity": "sha512-hDWMjlKzc18W2E4OeV3hUP8ohRJNHPD4Wd1+AQJj8zshZyCRT0usrvnExgbNUTo/vntDqCGMzgYWbXxyaA+L4g==", "license": "MIT", + "peer": true, "peerDependencies": { "@ton/crypto": ">=3.2.0" } }, "node_modules/@ton/crypto": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@ton/crypto/-/crypto-3.3.0.tgz", - "integrity": "sha512-/A6CYGgA/H36OZ9BbTaGerKtzWp50rg67ZCH2oIjV1NcrBaCK9Z343M+CxedvM7Haf3f/Ee9EhxyeTp0GKMUpA==", "license": "MIT", "dependencies": { "@ton/crypto-primitives": "2.1.0", @@ -10124,8 +9632,6 @@ }, "node_modules/@ton/crypto-primitives": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@ton/crypto-primitives/-/crypto-primitives-2.1.0.tgz", - "integrity": "sha512-PQesoyPgqyI6vzYtCXw4/ZzevePc4VGcJtFwf08v10OevVJHVfW238KBdpj1kEDQkxWLeuNHEpTECNFKnP6tow==", "license": "MIT", "dependencies": { "jssha": "3.2.0" @@ -10133,8 +9639,6 @@ }, "node_modules/@ton/ton": { "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@ton/ton/-/ton-16.3.0.tgz", - "integrity": "sha512-iafYCLYVD2+ZrqbnJA7CvuZ13hUVQSmmOAxx8Ozo2pgl0HHjj004hdoHzrdE0XCHdj7AOZB3zcZaHBVJxQOh9Q==", "license": "MIT", "dependencies": { "axios": "^1.15.0", @@ -10146,20 +9650,8 @@ "@ton/crypto": ">=3.2.0" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/bn.js": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -10168,8 +9660,6 @@ }, "node_modules/@types/body-parser": { "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "license": "MIT", "dependencies": { "@types/connect": "*", @@ -10178,8 +9668,6 @@ }, "node_modules/@types/bonjour": { "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", - "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -10187,15 +9675,11 @@ }, "node_modules/@types/configstore": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/configstore/-/configstore-6.0.2.tgz", - "integrity": "sha512-OS//b51j9uyR3zvwD04Kfs5kHpve2qalQ18JhY/ho3voGYUTPLEG90/ocfKPI48hyHH8T04f7KEEbK6Ue60oZQ==", "dev": true, "license": "MIT" }, "node_modules/@types/connect": { "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -10203,8 +9687,6 @@ }, "node_modules/@types/connect-history-api-fallback": { "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", - "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "license": "MIT", "dependencies": { "@types/express-serve-static-core": "*", @@ -10213,8 +9695,6 @@ }, "node_modules/@types/d3": { "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", - "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", "license": "MIT", "dependencies": { "@types/d3-array": "*", @@ -10251,14 +9731,10 @@ }, "node_modules/@types/d3-array": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", "license": "MIT" }, "node_modules/@types/d3-axis": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", - "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", "license": "MIT", "dependencies": { "@types/d3-selection": "*" @@ -10266,8 +9742,6 @@ }, "node_modules/@types/d3-brush": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", - "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", "license": "MIT", "dependencies": { "@types/d3-selection": "*" @@ -10275,20 +9749,14 @@ }, "node_modules/@types/d3-chord": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", - "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", "license": "MIT" }, "node_modules/@types/d3-color": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", "license": "MIT" }, "node_modules/@types/d3-contour": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", - "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", "license": "MIT", "dependencies": { "@types/d3-array": "*", @@ -10297,20 +9765,14 @@ }, "node_modules/@types/d3-delaunay": { "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", "license": "MIT" }, "node_modules/@types/d3-dispatch": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", - "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", "license": "MIT" }, "node_modules/@types/d3-drag": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", - "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", "license": "MIT", "dependencies": { "@types/d3-selection": "*" @@ -10318,20 +9780,14 @@ }, "node_modules/@types/d3-dsv": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", - "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", "license": "MIT" }, "node_modules/@types/d3-ease": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", "license": "MIT" }, "node_modules/@types/d3-fetch": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", - "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", "license": "MIT", "dependencies": { "@types/d3-dsv": "*" @@ -10339,20 +9795,14 @@ }, "node_modules/@types/d3-force": { "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", - "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", "license": "MIT" }, "node_modules/@types/d3-format": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", - "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", "license": "MIT" }, "node_modules/@types/d3-geo": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.1.tgz", - "integrity": "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==", "license": "MIT", "dependencies": { "@types/geojson": "*" @@ -10360,14 +9810,10 @@ }, "node_modules/@types/d3-hierarchy": { "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", - "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", "license": "MIT" }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", "license": "MIT", "dependencies": { "@types/d3-color": "*" @@ -10375,32 +9821,22 @@ }, "node_modules/@types/d3-path": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", "license": "MIT" }, "node_modules/@types/d3-polygon": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", - "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", "license": "MIT" }, "node_modules/@types/d3-quadtree": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", - "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", "license": "MIT" }, "node_modules/@types/d3-random": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.4.tgz", - "integrity": "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==", "license": "MIT" }, "node_modules/@types/d3-scale": { "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", - "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", "license": "MIT", "dependencies": { "@types/d3-time": "*" @@ -10408,20 +9844,14 @@ }, "node_modules/@types/d3-scale-chromatic": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", "license": "MIT" }, "node_modules/@types/d3-selection": { "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", - "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", "license": "MIT" }, "node_modules/@types/d3-shape": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==", "license": "MIT", "dependencies": { "@types/d3-path": "*" @@ -10429,26 +9859,18 @@ }, "node_modules/@types/d3-time": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", - "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", "license": "MIT" }, "node_modules/@types/d3-time-format": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", - "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", "license": "MIT" }, "node_modules/@types/d3-timer": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT" }, "node_modules/@types/d3-transition": { "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", - "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", "license": "MIT", "dependencies": { "@types/d3-selection": "*" @@ -10456,8 +9878,6 @@ }, "node_modules/@types/d3-zoom": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", - "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", "license": "MIT", "dependencies": { "@types/d3-interpolate": "*", @@ -10466,8 +9886,6 @@ }, "node_modules/@types/debug": { "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", - "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", "dependencies": { "@types/ms": "*" @@ -10475,14 +9893,10 @@ }, "node_modules/@types/estree": { "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "license": "MIT" }, "node_modules/@types/estree-jsx": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", - "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", "license": "MIT", "dependencies": { "@types/estree": "*" @@ -10490,8 +9904,6 @@ }, "node_modules/@types/express": { "version": "4.17.25", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", - "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -10502,8 +9914,6 @@ }, "node_modules/@types/express-serve-static-core": { "version": "4.19.9", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.9.tgz", - "integrity": "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -10514,14 +9924,10 @@ }, "node_modules/@types/geojson": { "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "license": "MIT" }, "node_modules/@types/hast": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", - "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -10529,32 +9935,22 @@ }, "node_modules/@types/history": { "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", "license": "MIT" }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", "license": "MIT" }, "node_modules/@types/http-cache-semantics": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", "license": "MIT" }, "node_modules/@types/http-errors": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "license": "MIT" }, "node_modules/@types/http-proxy": { "version": "1.17.17", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", - "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -10562,14 +9958,10 @@ }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" @@ -10577,8 +9969,6 @@ }, "node_modules/@types/istanbul-reports": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" @@ -10586,14 +9976,11 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/mdast": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -10601,20 +9988,14 @@ }, "node_modules/@types/mdx": { "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz", - "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==", "license": "MIT" }, "node_modules/@types/mime": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "license": "MIT" }, "node_modules/@types/ms": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, "node_modules/@types/node": { @@ -10622,50 +10003,41 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.1.tgz", "integrity": "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~8.3.0" } }, "node_modules/@types/prismjs": { "version": "1.26.6", - "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", - "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", "license": "MIT" }, "node_modules/@types/qs": { "version": "6.15.1", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "license": "MIT" }, "node_modules/@types/react": { "version": "19.2.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", - "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.7.tgz", - "integrity": "sha512-I8bPpDLcHBv1qiIiXDCy71Rt8eQDKJP0sMSWJphDdAcdqiJ1sGpZamavoEIRZmYzjia9LuEb2HlYdDpmoENpvQ==", + "version": "19.2.5", "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } }, "node_modules/@types/react-router": { "version": "5.1.20", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", - "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", "license": "MIT", "dependencies": { "@types/history": "^4.7.11", @@ -10674,8 +10046,6 @@ }, "node_modules/@types/react-router-config": { "version": "5.0.11", - "resolved": "https://registry.npmjs.org/@types/react-router-config/-/react-router-config-5.0.11.tgz", - "integrity": "sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==", "license": "MIT", "dependencies": { "@types/history": "^4.7.11", @@ -10685,8 +10055,6 @@ }, "node_modules/@types/react-router-dom": { "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", "license": "MIT", "dependencies": { "@types/history": "^4.7.11", @@ -10696,14 +10064,10 @@ }, "node_modules/@types/retry": { "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", "license": "MIT" }, "node_modules/@types/sax": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", - "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -10711,8 +10075,6 @@ }, "node_modules/@types/send": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -10720,8 +10082,6 @@ }, "node_modules/@types/serve-index": { "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", - "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", "license": "MIT", "dependencies": { "@types/express": "*" @@ -10729,8 +10089,6 @@ }, "node_modules/@types/serve-static": { "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -10740,8 +10098,6 @@ }, "node_modules/@types/serve-static/node_modules/@types/send": { "version": "0.17.6", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", "license": "MIT", "dependencies": { "@types/mime": "^1", @@ -10750,8 +10106,6 @@ }, "node_modules/@types/sockjs": { "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", - "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -10759,21 +10113,15 @@ }, "node_modules/@types/trusted-types": { "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT", "optional": true }, "node_modules/@types/unist": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, "node_modules/@types/update-notifier": { "version": "6.0.8", - "resolved": "https://registry.npmjs.org/@types/update-notifier/-/update-notifier-6.0.8.tgz", - "integrity": "sha512-IlDFnfSVfYQD+cKIg63DEXn3RFmd7W1iYtKQsJodcHK9R1yr8aKbKaPKfBxzPpcHCq2DU8zUq4PIPmy19Thjfg==", "dev": true, "license": "MIT", "dependencies": { @@ -10783,8 +10131,6 @@ }, "node_modules/@types/update-notifier/node_modules/boxen": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", - "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", "dev": true, "license": "MIT", "dependencies": { @@ -10806,8 +10152,6 @@ }, "node_modules/@types/update-notifier/node_modules/camelcase": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", - "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", "dev": true, "license": "MIT", "engines": { @@ -10819,8 +10163,6 @@ }, "node_modules/@types/update-notifier/node_modules/chalk": { "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, "license": "MIT", "engines": { @@ -10830,10 +10172,29 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@types/update-notifier/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/update-notifier/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@types/update-notifier/node_modules/type-fest": { "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -10843,28 +10204,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@types/update-notifier/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", - "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", "license": "MIT" }, "node_modules/@types/uuid": { "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", "license": "MIT" }, "node_modules/@types/w3c-web-usb": { "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.14.tgz", - "integrity": "sha512-Qu3Nn6JFuF4+sHKYl+IcX9vYiI40ogleXzFFSxoE1W94rG98o/kXs8uJ0QSfFzuwBCZWlGfUGpPkgwuuX4PchA==", "license": "MIT" }, "node_modules/@types/ws": { "version": "7.4.7", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", - "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -10872,8 +10241,6 @@ }, "node_modules/@types/yargs": { "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -10881,15 +10248,11 @@ }, "node_modules/@types/yargs-parser": { "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "license": "MIT" }, "node_modules/@typescript/native": { "name": "typescript", "version": "7.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", - "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -10924,8 +10287,6 @@ "node_modules/@typescript/old": { "name": "typescript", "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -10954,8 +10315,6 @@ }, "node_modules/@typescript/typescript-darwin-arm64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", - "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", "cpu": [ "arm64" ], @@ -11276,15 +10635,11 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", - "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "version": "1.3.3", "license": "ISC" }, "node_modules/@upsetjs/venn.js": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", - "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", "license": "MIT", "optionalDependencies": { "d3-selection": "^3.0.0", @@ -11293,8 +10648,6 @@ }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "license": "MIT", "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", @@ -11303,26 +10656,18 @@ }, "node_modules/@webassemblyjs/floating-point-hex-parser": { "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "license": "MIT", "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", @@ -11332,14 +10677,10 @@ }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -11350,8 +10691,6 @@ }, "node_modules/@webassemblyjs/ieee754": { "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" @@ -11359,8 +10698,6 @@ }, "node_modules/@webassemblyjs/leb128": { "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" @@ -11368,14 +10705,10 @@ }, "node_modules/@webassemblyjs/utf8": { "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -11390,8 +10723,6 @@ }, "node_modules/@webassemblyjs/wasm-gen": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -11403,8 +10734,6 @@ }, "node_modules/@webassemblyjs/wasm-opt": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -11415,8 +10744,6 @@ }, "node_modules/@webassemblyjs/wasm-parser": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -11429,8 +10756,6 @@ }, "node_modules/@webassemblyjs/wast-printer": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -11439,21 +10764,39 @@ }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "license": "Apache-2.0" }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@zksync/contracts": { + "name": "era-contracts", + "version": "0.1.0", + "resolved": "git+ssh://git@github.com/matter-labs/era-contracts.git#446d391d34bdb48255d5f8fef8a8248925fc98b9", + "integrity": "sha512-KhgPVqd/MgV/ICUEsQf1uyL321GNPqsyHSAPMCaa9vW94fbuQK6RwMWoyQOPlZP17cQD8tzLNCSXqz73652kow==", + "dev": true, + "workspaces": { + "packages": [ + "l1-contracts", + "l2-contracts", + "system-contracts", + "gas-bound-caller" + ], + "nohoist": [ + "**/@openzeppelin/**" + ] + } + }, "node_modules/abitype": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.3.0.tgz", - "integrity": "sha512-fk6Te+bojIFrMvMZrnOO+SxCB+RUksTGOzq/60ZRvs1L+BVzvi2bqt9L3W/17ZLdZsyM1FuYf65P5nlmoiH1Bg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/wevm" }, @@ -11472,8 +10815,6 @@ }, "node_modules/accepts": { "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", "dependencies": { "mime-types": "~2.1.34", @@ -11485,8 +10826,6 @@ }, "node_modules/accepts/node_modules/negotiator": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -11494,9 +10833,8 @@ }, "node_modules/acorn": { "version": "8.18.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", - "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -11506,8 +10844,6 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -11515,8 +10851,6 @@ }, "node_modules/acorn-walk": { "version": "8.3.5", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", - "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -11527,8 +10861,6 @@ }, "node_modules/address": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/address/-/address-2.0.3.tgz", - "integrity": "sha512-XNAb/a6TCqou+TufU8/u11HCu9x1gYvOoxLwtlXgIqmkrYQADVv6ljyW2zwiPhHz9R1gItAWpuDrdJMmrOBFEA==", "license": "MIT", "engines": { "node": ">= 16.0.0" @@ -11536,14 +10868,10 @@ }, "node_modules/aes-js": { "version": "4.0.0-beta.5", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", - "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", "license": "MIT" }, "node_modules/agent-base": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", "dependencies": { "debug": "4" @@ -11554,8 +10882,6 @@ }, "node_modules/agentkeepalive": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", "license": "MIT", "dependencies": { "humanize-ms": "^1.2.1" @@ -11566,8 +10892,6 @@ }, "node_modules/aggregate-error": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "license": "MIT", "dependencies": { "clean-stack": "^2.0.0", @@ -11580,9 +10904,8 @@ "node_modules/ajv": { "name": "@redocly/ajv", "version": "8.18.3", - "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.18.3.tgz", - "integrity": "sha512-l42u0of3hY98sN2A+M4qTX1O/KrpgGH32Hu9kP2GtHyD5Dfqq86PKFLe5dwaD8DEnNmlOlll2BAmeEtf0DaySg==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -11596,8 +10919,6 @@ }, "node_modules/ajv-draft-04": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", - "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", "license": "MIT", "peerDependencies": { "ajv": "^8.5.0" @@ -11610,8 +10931,6 @@ }, "node_modules/ajv-formats": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -11627,8 +10946,6 @@ }, "node_modules/ajv-keywords": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" @@ -11639,9 +10956,8 @@ }, "node_modules/algoliasearch": { "version": "5.57.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.57.0.tgz", - "integrity": "sha512-HpND7MBGctOAkd1GoQoDZCGoCpqNTS5NG1LuhElFet3RdLJkwnyTYZXZhXwtpAQPrI36fqQ3eT6KQrdKDTKu3A==", "license": "MIT", + "peer": true, "dependencies": { "@algolia/abtesting": "1.23.0", "@algolia/client-abtesting": "5.57.0", @@ -11664,8 +10980,6 @@ }, "node_modules/algoliasearch-helper": { "version": "3.29.3", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.29.3.tgz", - "integrity": "sha512-gVOMbbPVrCO3Xs+B+BLAIGFqIQow6qHMTYCEeBqLQB9m4ZmKUqMwkEumlal2iyyezHEd4Y0FvFTLbCRutCutIQ==", "license": "MIT", "dependencies": { "@algolia/events": "^4.0.1" @@ -11676,8 +10990,6 @@ }, "node_modules/allof-merge": { "version": "0.6.8", - "resolved": "https://registry.npmjs.org/allof-merge/-/allof-merge-0.6.8.tgz", - "integrity": "sha512-RJrHVDqITsU1kjE2L7s1hy4AYZSTlO1m9jTleYhVCEOfOpbbygRGfcEgrp+bW3oX/PcMUwVkt6MSJyXoyI6lRA==", "license": "MIT", "dependencies": { "json-crawl": "^0.5.3" @@ -11685,23 +10997,24 @@ }, "node_modules/ansi-align": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", "license": "ISC", "dependencies": { "string-width": "^4.1.0" } }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-align/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "node_modules/ansi-align/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -11712,10 +11025,26 @@ "node": ">=8" } }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-html-community": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", "engines": [ "node >= 0.8.0" ], @@ -11725,24 +11054,20 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.3.0", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "6.2.3", "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -11750,8 +11075,6 @@ }, "node_modules/ansis": { "version": "3.17.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", - "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", "license": "ISC", "engines": { "node": ">=14" @@ -11759,14 +11082,10 @@ }, "node_modules/any-promise": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -11778,26 +11097,18 @@ }, "node_modules/arg": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, "node_modules/array-flatten": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, "node_modules/array-union": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "license": "MIT", "engines": { "node": ">=8" @@ -11805,8 +11116,6 @@ }, "node_modules/asn1js": { "version": "3.0.10", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", - "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", "license": "BSD-3-Clause", "dependencies": { "pvtsutils": "^1.3.6", @@ -11817,10 +11126,16 @@ "node": ">=12.0.0" } }, + "node_modules/assertion-error": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/astring": { "version": "1.9.0", - "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", - "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", "license": "MIT", "bin": { "astring": "bin/astring" @@ -11828,20 +11143,22 @@ }, "node_modules/async": { "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/at-least-node": { + "version": "1.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/atomically": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", - "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", "license": "MIT", "dependencies": { "stubborn-fs": "^2.0.0", @@ -11850,8 +11167,6 @@ }, "node_modules/autoprefixer": { "version": "10.5.4", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz", - "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", "funding": [ { "type": "opencollective", @@ -11898,8 +11213,6 @@ }, "node_modules/babel-loader": { "version": "9.2.1", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", - "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", "license": "MIT", "dependencies": { "find-cache-dir": "^4.0.0", @@ -11915,8 +11228,6 @@ }, "node_modules/babel-plugin-dynamic-import-node": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", "license": "MIT", "dependencies": { "object.assign": "^4.1.0" @@ -11924,8 +11235,6 @@ }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.17", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", - "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", "license": "MIT", "dependencies": { "@babel/compat-data": "^7.28.6", @@ -11938,8 +11247,6 @@ }, "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -11947,8 +11254,6 @@ }, "node_modules/babel-plugin-polyfill-corejs3": { "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", "license": "MIT", "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", @@ -11960,8 +11265,6 @@ }, "node_modules/babel-plugin-polyfill-regenerator": { "version": "0.6.8", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", - "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", "license": "MIT", "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8" @@ -11972,8 +11275,6 @@ }, "node_modules/bail": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", "license": "MIT", "funding": { "type": "github", @@ -11982,8 +11283,6 @@ }, "node_modules/balanced-match": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", "engines": { @@ -11992,14 +11291,10 @@ }, "node_modules/base-x": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", - "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "funding": [ { "type": "github", @@ -12017,9 +11312,7 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.11.20", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", - "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", + "version": "2.11.19", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -12030,14 +11323,26 @@ }, "node_modules/batch": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", "license": "MIT" }, + "node_modules/bech32": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/better-path-resolve": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-windows": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/big.js": { "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "license": "MIT", "engines": { "node": "*" @@ -12046,8 +11351,6 @@ "node_modules/bigint-buffer": { "name": "@trufflesuite/bigint-buffer", "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@trufflesuite/bigint-buffer/-/bigint-buffer-1.1.10.tgz", - "integrity": "sha512-pYIQC5EcMmID74t26GCC67946mgTJFiLXOT/BYozgrd4UEY2JHEGLhWi9cMiQCt5BSqFEvKkCHNnoj82SRjiEw==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -12059,8 +11362,6 @@ }, "node_modules/bignumber.js": { "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", "license": "MIT", "engines": { "node": "*" @@ -12068,8 +11369,6 @@ }, "node_modules/binary-extensions": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "license": "MIT", "engines": { "node": ">=8" @@ -12080,8 +11379,6 @@ }, "node_modules/bindings": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "license": "MIT", "dependencies": { "file-uri-to-path": "1.0.0" @@ -12089,14 +11386,10 @@ }, "node_modules/bip32-path": { "version": "0.4.2", - "resolved": "https://registry.npmjs.org/bip32-path/-/bip32-path-0.4.2.tgz", - "integrity": "sha512-ZBMCELjJfcNMkz5bDuJ1WrYvjlhEF5k6mQ8vUr4N7MbVRsXei7ZOg8VhhwMfNiW68NWmLkgkc6WvTickrLGprQ==", "license": "MIT" }, "node_modules/bl": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -12106,8 +11399,6 @@ }, "node_modules/bl/node_modules/buffer": { "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "funding": [ { "type": "github", @@ -12130,14 +11421,10 @@ }, "node_modules/bn.js": { "version": "5.2.5", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.5.tgz", - "integrity": "sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==", "license": "MIT" }, "node_modules/body-parser": { "version": "1.20.6", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", - "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -12160,8 +11447,6 @@ }, "node_modules/body-parser/node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -12169,8 +11454,6 @@ }, "node_modules/body-parser/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -12178,8 +11461,6 @@ }, "node_modules/body-parser/node_modules/iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -12190,14 +11471,10 @@ }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/bonjour-service": { "version": "1.4.4", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.4.4.tgz", - "integrity": "sha512-jCZcVv7eoc4QesRscwEZtSROBen+6LpKAmBIsQYQrsAeVHLyMXWX/t6eIV5KiRZYNUBl8eVqImEEMQ8L5+c/Kw==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -12206,20 +11483,14 @@ }, "node_modules/boolbase": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "license": "ISC" }, "node_modules/borsh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/borsh/-/borsh-2.0.0.tgz", - "integrity": "sha512-kc9+BgR3zz9+cjbwM8ODoUB4fs3X3I5A/HtX7LZKxCLaMrEeDFoBpnhZY//DTS1VZBSs6S5v46RZRbZjRFspEg==", "license": "Apache-2.0" }, "node_modules/boxen": { "version": "6.2.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", - "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", "license": "MIT", "dependencies": { "ansi-align": "^3.0.1", @@ -12238,10 +11509,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/boxen/node_modules/emoji-regex": { + "version": "9.2.2", + "license": "MIT" + }, + "node_modules/boxen/node_modules/string-width": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/boxen/node_modules/type-fest": { "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=12.20" @@ -12250,10 +11538,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/boxen/node_modules/wrap-ansi": { + "version": "8.1.0", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/brace-expansion": { "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { @@ -12265,8 +11566,6 @@ }, "node_modules/braces": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -12277,14 +11576,10 @@ }, "node_modules/brorand": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", "license": "MIT" }, "node_modules/browserslist": { "version": "4.28.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", - "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", "funding": [ { "type": "opencollective", @@ -12300,6 +11595,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", @@ -12316,8 +11612,6 @@ }, "node_modules/bs58": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", - "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", "license": "MIT", "dependencies": { "base-x": "^5.0.0" @@ -12325,8 +11619,6 @@ }, "node_modules/buffer": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "funding": [ { "type": "github", @@ -12349,14 +11641,10 @@ }, "node_modules/buffer-from": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, "node_modules/buffer-layout": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/buffer-layout/-/buffer-layout-1.2.2.tgz", - "integrity": "sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==", "license": "MIT", "engines": { "node": ">=4.5" @@ -12364,8 +11652,6 @@ }, "node_modules/bufferutil": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", - "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", "hasInstallScript": true, "license": "MIT", "optional": true, @@ -12376,10 +11662,16 @@ "node": ">=6.14.2" } }, + "node_modules/bufio": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/bundle-name": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", "license": "MIT", "dependencies": { "run-applescript": "^7.0.0" @@ -12405,8 +11697,6 @@ }, "node_modules/bytes": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -12414,8 +11704,6 @@ }, "node_modules/bytestreamjs": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", - "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", "license": "BSD-3-Clause", "engines": { "node": ">=6.0.0" @@ -12423,8 +11711,6 @@ }, "node_modules/c8": { "version": "12.0.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-12.0.0.tgz", - "integrity": "sha512-4zpJvrd1nKWutnnKC2pXkFmb6iM1l+ffN//o1CzlTNwW7GSOs9a1xrLqkC48nU8oEkjmPZLPiwMsIaOvoF4Pqg==", "dev": true, "license": "ISC", "dependencies": { @@ -12521,8 +11807,6 @@ }, "node_modules/call-bind": { "version": "1.0.9", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", - "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -12539,8 +11823,6 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -12552,8 +11834,6 @@ }, "node_modules/call-bound": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -12568,14 +11848,10 @@ }, "node_modules/call-me-maybe": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", - "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", "license": "MIT" }, "node_modules/callsites": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "license": "MIT", "engines": { "node": ">=6" @@ -12583,8 +11859,6 @@ }, "node_modules/camel-case": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", "license": "MIT", "dependencies": { "pascal-case": "^3.1.2", @@ -12593,8 +11867,6 @@ }, "node_modules/camelcase": { "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "license": "MIT", "engines": { "node": ">=10" @@ -12605,8 +11877,6 @@ }, "node_modules/camelcase-css": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", "license": "MIT", "engines": { "node": ">= 6" @@ -12614,8 +11884,6 @@ }, "node_modules/caniuse-api": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", "license": "MIT", "dependencies": { "browserslist": "^4.0.0", @@ -12626,8 +11894,6 @@ }, "node_modules/caniuse-lite": { "version": "1.0.30001810", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", - "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", "funding": [ { "type": "opencollective", @@ -12646,41 +11912,63 @@ }, "node_modules/ccount": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chai": { + "version": "4.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/change-case": { "version": "5.4.4", - "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", - "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", "dev": true, "license": "MIT" }, "node_modules/char-regex": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "license": "MIT", "engines": { "node": ">=10" @@ -12688,8 +11976,6 @@ }, "node_modules/character-entities": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "license": "MIT", "funding": { "type": "github", @@ -12698,8 +11984,6 @@ }, "node_modules/character-entities-html4": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", "license": "MIT", "funding": { "type": "github", @@ -12708,8 +11992,6 @@ }, "node_modules/character-entities-legacy": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "license": "MIT", "funding": { "type": "github", @@ -12718,8 +12000,6 @@ }, "node_modules/character-reference-invalid": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", "license": "MIT", "funding": { "type": "github", @@ -12728,23 +12008,28 @@ }, "node_modules/chardet": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", - "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", "license": "MIT" }, "node_modules/charset": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz", - "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==", "license": "MIT", "engines": { "node": ">=4.0.0" } }, + "node_modules/check-error": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, "node_modules/cheerio": { "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", - "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", "license": "MIT", "dependencies": { "cheerio-select": "^2.1.0", @@ -12764,8 +12049,6 @@ }, "node_modules/cheerio-select": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", @@ -12781,8 +12064,6 @@ }, "node_modules/chokidar": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -12805,8 +12086,6 @@ }, "node_modules/chownr": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -12815,8 +12094,6 @@ }, "node_modules/chrome-trace-event": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "license": "MIT", "engines": { "node": ">=6.0" @@ -12836,8 +12113,6 @@ }, "node_modules/ci-info": { "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "funding": [ { "type": "github", @@ -12851,8 +12126,6 @@ }, "node_modules/clean-css": { "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", "license": "MIT", "dependencies": { "source-map": "~0.6.0" @@ -12863,8 +12136,6 @@ }, "node_modules/clean-css/node_modules/source-map": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -12872,8 +12143,6 @@ }, "node_modules/clean-stack": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "license": "MIT", "engines": { "node": ">=6" @@ -12881,8 +12150,6 @@ }, "node_modules/cli-boxes": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", "license": "MIT", "engines": { "node": ">=10" @@ -12893,8 +12160,6 @@ }, "node_modules/cli-table3": { "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "license": "MIT", "dependencies": { "string-width": "^4.2.0" @@ -12906,16 +12171,19 @@ "@colors/colors": "1.5.0" } }, + "node_modules/cli-table3/node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cli-table3/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "node_modules/cli-table3/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -12926,6 +12194,16 @@ "node": ">=8" } }, + "node_modules/cli-table3/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cli-width": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", @@ -12937,8 +12215,6 @@ }, "node_modules/cliui": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "license": "ISC", "dependencies": { "string-width": "^7.2.0", @@ -12949,40 +12225,8 @@ "node": ">=20" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", - "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, "node_modules/cliui/node_modules/string-width": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -12996,42 +12240,8 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/clone-deep": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "license": "MIT", "dependencies": { "is-plain-object": "^2.0.4", @@ -13044,8 +12254,6 @@ }, "node_modules/clsx": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", "engines": { "node": ">=6" @@ -13053,8 +12261,6 @@ }, "node_modules/collapse-white-space": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", - "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", "license": "MIT", "funding": { "type": "github", @@ -13063,8 +12269,6 @@ }, "node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -13075,26 +12279,18 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, "node_modules/colord": { "version": "2.10.0", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.10.0.tgz", - "integrity": "sha512-AidJptpBJmjTclAp9BkLwJi0T93fo5epJnbaZslpg6QVzpHjAiveF55mE9AcUJiGMqRHgMDY8soMsQtuNYMHfw==", "license": "MIT" }, "node_modules/colorette": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", "license": "MIT" }, "node_modules/combine-promises": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.2.0.tgz", - "integrity": "sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==", "license": "MIT", "engines": { "node": ">=10" @@ -13102,8 +12298,6 @@ }, "node_modules/combined-stream": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -13114,14 +12308,10 @@ }, "node_modules/comlink": { "version": "4.4.2", - "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.2.tgz", - "integrity": "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==", "license": "Apache-2.0" }, "node_modules/comma-separated-tokens": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "license": "MIT", "funding": { "type": "github", @@ -13130,8 +12320,6 @@ }, "node_modules/commander": { "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "license": "MIT", "engines": { "node": ">=18" @@ -13139,14 +12327,10 @@ }, "node_modules/common-path-prefix": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", "license": "ISC" }, "node_modules/compressible": { "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" @@ -13157,8 +12341,6 @@ }, "node_modules/compression": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -13175,8 +12357,6 @@ }, "node_modules/compression/node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -13184,8 +12364,6 @@ }, "node_modules/compression/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -13193,14 +12371,10 @@ }, "node_modules/compression/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/compute-gcd": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz", - "integrity": "sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==", "dependencies": { "validate.io-array": "^1.0.3", "validate.io-function": "^1.0.2", @@ -13209,8 +12383,6 @@ }, "node_modules/compute-lcm": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz", - "integrity": "sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==", "dependencies": { "compute-gcd": "^1.2.1", "validate.io-array": "^1.0.3", @@ -13220,14 +12392,10 @@ }, "node_modules/concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, "node_modules/config-chain": { "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", "license": "MIT", "dependencies": { "ini": "^1.3.4", @@ -13236,8 +12404,6 @@ }, "node_modules/configstore": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-7.1.0.tgz", - "integrity": "sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg==", "license": "BSD-2-Clause", "dependencies": { "atomically": "^2.0.3", @@ -13254,8 +12420,6 @@ }, "node_modules/connect-history-api-fallback": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", "license": "MIT", "engines": { "node": ">=0.8" @@ -13263,8 +12427,6 @@ }, "node_modules/consola": { "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", "license": "MIT", "engines": { "node": "^14.18.0 || >=16.10.0" @@ -13272,8 +12434,6 @@ }, "node_modules/content-disposition": { "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -13281,8 +12441,6 @@ }, "node_modules/content-type": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -13290,14 +12448,10 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "license": "MIT" }, "node_modules/cookie": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -13305,14 +12459,10 @@ }, "node_modules/cookie-signature": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, "node_modules/copy-text-to-clipboard": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.2.tgz", - "integrity": "sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==", "license": "MIT", "engines": { "node": ">=12" @@ -13323,8 +12473,6 @@ }, "node_modules/copy-webpack-plugin": { "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", "license": "MIT", "dependencies": { "fast-glob": "^3.2.11", @@ -13347,8 +12495,6 @@ }, "node_modules/copy-webpack-plugin/node_modules/glob-parent": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -13359,8 +12505,6 @@ }, "node_modules/copy-webpack-plugin/node_modules/globby": { "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", "license": "MIT", "dependencies": { "dir-glob": "^3.0.1", @@ -13378,8 +12522,6 @@ }, "node_modules/copy-webpack-plugin/node_modules/slash": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", "license": "MIT", "engines": { "node": ">=12" @@ -13390,8 +12532,6 @@ }, "node_modules/core-js": { "version": "3.50.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.50.0.tgz", - "integrity": "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==", "hasInstallScript": true, "license": "MIT", "engines": { @@ -13404,8 +12544,6 @@ }, "node_modules/core-js-compat": { "version": "3.50.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.50.0.tgz", - "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", "license": "MIT", "dependencies": { "browserslist": "^4.28.7" @@ -13420,14 +12558,10 @@ }, "node_modules/core-util-is": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, "node_modules/cose-base": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", - "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", "license": "MIT", "dependencies": { "layout-base": "^1.0.0" @@ -13435,8 +12569,6 @@ }, "node_modules/cosmiconfig": { "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "license": "MIT", "dependencies": { "import-fresh": "^3.3.0", @@ -13461,8 +12593,6 @@ }, "node_modules/cross-fetch": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", - "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", "license": "MIT", "dependencies": { "node-fetch": "^2.7.0" @@ -13470,8 +12600,6 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -13484,8 +12612,6 @@ }, "node_modules/crypto-hash": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/crypto-hash/-/crypto-hash-1.3.0.tgz", - "integrity": "sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg==", "license": "MIT", "engines": { "node": ">=8" @@ -13496,15 +12622,10 @@ }, "node_modules/crypto-js": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", - "deprecated": "Active development of CryptoJS has been discontinued. This library is no longer maintained.", "license": "MIT" }, "node_modules/crypto-random-string": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", "license": "MIT", "dependencies": { "type-fest": "^1.0.1" @@ -13518,8 +12639,6 @@ }, "node_modules/crypto-random-string/node_modules/type-fest": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" @@ -13530,8 +12649,6 @@ }, "node_modules/css-blank-pseudo": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", - "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", "funding": [ { "type": "github", @@ -13554,9 +12671,7 @@ } }, "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "version": "7.1.5", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -13568,8 +12683,6 @@ }, "node_modules/css-declaration-sorter": { "version": "7.4.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.4.0.tgz", - "integrity": "sha512-LTuzjPoyA2vMGKKcaOqKSp7Ub2eGrNfKiZH4LpezxpNrsICGCSFvsQOI29psISxNZtaXibkC2CXzrQ5enMeGGw==", "license": "ISC", "engines": { "node": "^14 || ^16 || >=18" @@ -13580,8 +12693,6 @@ }, "node_modules/css-has-pseudo": { "version": "7.0.3", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz", - "integrity": "sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==", "funding": [ { "type": "github", @@ -13607,8 +12718,6 @@ }, "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", "funding": [ { "type": "github", @@ -13628,10 +12737,9 @@ } }, "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "version": "7.1.5", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -13642,8 +12750,6 @@ }, "node_modules/css-loader": { "version": "6.11.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", - "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", @@ -13677,8 +12783,6 @@ }, "node_modules/css-minimizer-webpack-plugin": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", - "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", @@ -13721,8 +12825,6 @@ }, "node_modules/css-prefers-color-scheme": { "version": "10.0.0", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", - "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", "funding": [ { "type": "github", @@ -13743,8 +12845,6 @@ }, "node_modules/css-select": { "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", @@ -13759,8 +12859,6 @@ }, "node_modules/css-tree": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", "license": "MIT", "dependencies": { "mdn-data": "2.0.30", @@ -13772,8 +12870,6 @@ }, "node_modules/css-what": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "license": "BSD-2-Clause", "engines": { "node": ">= 6" @@ -13783,9 +12879,7 @@ } }, "node_modules/cssdb": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.11.0.tgz", - "integrity": "sha512-VzY/8kcK5M8oCVr/cwh24J8XVlCOITpmzCizKBC28o7Z1s23MMojXoJ3q7a+TQQoMKvxC3YlG0Z9yU10kJF1uQ==", + "version": "8.10.0", "funding": [ { "type": "opencollective", @@ -13800,8 +12894,6 @@ }, "node_modules/cssesc": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -13812,8 +12904,6 @@ }, "node_modules/cssnano": { "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", - "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", "license": "MIT", "dependencies": { "cssnano-preset-default": "^6.1.2", @@ -13832,8 +12922,6 @@ }, "node_modules/cssnano-preset-advanced": { "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", - "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", "license": "MIT", "dependencies": { "autoprefixer": "^10.4.19", @@ -13853,8 +12941,6 @@ }, "node_modules/cssnano-preset-default": { "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", - "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -13897,8 +12983,6 @@ }, "node_modules/cssnano-utils": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", - "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" @@ -13909,8 +12993,6 @@ }, "node_modules/csso": { "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", "license": "MIT", "dependencies": { "css-tree": "~2.2.0" @@ -13922,8 +13004,6 @@ }, "node_modules/csso/node_modules/css-tree": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", "license": "MIT", "dependencies": { "mdn-data": "2.0.28", @@ -13936,29 +13016,22 @@ }, "node_modules/csso/node_modules/mdn-data": { "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", "license": "CC0-1.0" }, "node_modules/csstype": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/cytoscape": { "version": "3.34.2", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.2.tgz", - "integrity": "sha512-Cm2jaj1X/PBNlzV9yH8zcfGOxO7U+CJ/+mxSBVPSchLaugdp4jtlGx5qaHtPRZ6tgiZ5P+o1XoRfJA+ba6KM3g==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10" } }, "node_modules/cytoscape-cose-bilkent": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", - "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", "license": "MIT", "dependencies": { "cose-base": "^1.0.0" @@ -13969,8 +13042,6 @@ }, "node_modules/cytoscape-fcose": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", - "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", "license": "MIT", "dependencies": { "cose-base": "^2.2.0" @@ -13981,8 +13052,6 @@ }, "node_modules/cytoscape-fcose/node_modules/cose-base": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", - "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", "license": "MIT", "dependencies": { "layout-base": "^2.0.0" @@ -13990,14 +13059,10 @@ }, "node_modules/cytoscape-fcose/node_modules/layout-base": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", - "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", "license": "MIT" }, "node_modules/d3": { "version": "7.9.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", - "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", "license": "ISC", "dependencies": { "d3-array": "3", @@ -14037,8 +13102,6 @@ }, "node_modules/d3-array": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", "license": "ISC", "dependencies": { "internmap": "1 - 2" @@ -14049,8 +13112,6 @@ }, "node_modules/d3-axis": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", "license": "ISC", "engines": { "node": ">=12" @@ -14058,8 +13119,6 @@ }, "node_modules/d3-brush": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -14074,8 +13133,6 @@ }, "node_modules/d3-chord": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", "license": "ISC", "dependencies": { "d3-path": "1 - 3" @@ -14086,8 +13143,6 @@ }, "node_modules/d3-color": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", "license": "ISC", "engines": { "node": ">=12" @@ -14095,8 +13150,6 @@ }, "node_modules/d3-contour": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", "license": "ISC", "dependencies": { "d3-array": "^3.2.0" @@ -14107,8 +13160,6 @@ }, "node_modules/d3-delaunay": { "version": "6.0.4", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", "license": "ISC", "dependencies": { "delaunator": "5" @@ -14119,8 +13170,6 @@ }, "node_modules/d3-dispatch": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", "license": "ISC", "engines": { "node": ">=12" @@ -14128,8 +13177,6 @@ }, "node_modules/d3-drag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -14141,8 +13188,6 @@ }, "node_modules/d3-dsv": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", "license": "ISC", "dependencies": { "commander": "7", @@ -14166,8 +13211,6 @@ }, "node_modules/d3-dsv/node_modules/commander": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", "engines": { "node": ">= 10" @@ -14175,8 +13218,6 @@ }, "node_modules/d3-dsv/node_modules/iconv-lite": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -14187,8 +13228,6 @@ }, "node_modules/d3-ease": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", "license": "BSD-3-Clause", "engines": { "node": ">=12" @@ -14196,8 +13235,6 @@ }, "node_modules/d3-fetch": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", "license": "ISC", "dependencies": { "d3-dsv": "1 - 3" @@ -14208,8 +13245,6 @@ }, "node_modules/d3-force": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -14222,8 +13257,6 @@ }, "node_modules/d3-format": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", - "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", "license": "ISC", "engines": { "node": ">=12" @@ -14231,8 +13264,6 @@ }, "node_modules/d3-geo": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", - "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", "license": "ISC", "dependencies": { "d3-array": "2.5.0 - 3" @@ -14243,8 +13274,6 @@ }, "node_modules/d3-hierarchy": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", "license": "ISC", "engines": { "node": ">=12" @@ -14252,8 +13281,6 @@ }, "node_modules/d3-interpolate": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", "license": "ISC", "dependencies": { "d3-color": "1 - 3" @@ -14264,8 +13291,6 @@ }, "node_modules/d3-path": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", "license": "ISC", "engines": { "node": ">=12" @@ -14273,8 +13298,6 @@ }, "node_modules/d3-polygon": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", "license": "ISC", "engines": { "node": ">=12" @@ -14282,8 +13305,6 @@ }, "node_modules/d3-quadtree": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", "license": "ISC", "engines": { "node": ">=12" @@ -14291,8 +13312,6 @@ }, "node_modules/d3-random": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", "license": "ISC", "engines": { "node": ">=12" @@ -14300,8 +13319,6 @@ }, "node_modules/d3-sankey": { "version": "0.12.3", - "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", - "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", "license": "BSD-3-Clause", "dependencies": { "d3-array": "1 - 2", @@ -14310,8 +13327,6 @@ }, "node_modules/d3-sankey/node_modules/d3-array": { "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", "license": "BSD-3-Clause", "dependencies": { "internmap": "^1.0.0" @@ -14319,14 +13334,10 @@ }, "node_modules/d3-sankey/node_modules/d3-path": { "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", "license": "BSD-3-Clause" }, "node_modules/d3-sankey/node_modules/d3-shape": { "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", "license": "BSD-3-Clause", "dependencies": { "d3-path": "1" @@ -14334,14 +13345,10 @@ }, "node_modules/d3-sankey/node_modules/internmap": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", "license": "ISC" }, "node_modules/d3-scale": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", "license": "ISC", "dependencies": { "d3-array": "2.10.0 - 3", @@ -14356,8 +13363,6 @@ }, "node_modules/d3-scale-chromatic": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", "license": "ISC", "dependencies": { "d3-color": "1 - 3", @@ -14369,17 +13374,14 @@ }, "node_modules/d3-selection": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } }, "node_modules/d3-shape": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "license": "ISC", "dependencies": { "d3-path": "^3.1.0" @@ -14390,8 +13392,6 @@ }, "node_modules/d3-time": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", "license": "ISC", "dependencies": { "d3-array": "2 - 3" @@ -14402,8 +13402,6 @@ }, "node_modules/d3-time-format": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "license": "ISC", "dependencies": { "d3-time": "1 - 3" @@ -14414,8 +13412,6 @@ }, "node_modules/d3-timer": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", "license": "ISC", "engines": { "node": ">=12" @@ -14423,8 +13419,6 @@ }, "node_modules/d3-transition": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", "license": "ISC", "dependencies": { "d3-color": "1 - 3", @@ -14442,8 +13436,6 @@ }, "node_modules/d3-zoom": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -14458,8 +13450,6 @@ }, "node_modules/dagre-d3-es": { "version": "7.0.14", - "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", - "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", "license": "MIT", "dependencies": { "d3": "^7.9.0", @@ -14468,26 +13458,18 @@ }, "node_modules/dataloader": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.3.tgz", - "integrity": "sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==", "license": "MIT" }, "node_modules/dayjs": { "version": "1.11.23", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", - "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", "license": "MIT" }, "node_modules/debounce": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -14503,8 +13485,6 @@ }, "node_modules/decode-named-character-reference": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "license": "MIT", "dependencies": { "character-entities": "^2.0.0" @@ -14516,8 +13496,6 @@ }, "node_modules/decompress-response": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "license": "MIT", "dependencies": { "mimic-response": "^3.1.0" @@ -14531,8 +13509,6 @@ }, "node_modules/decompress-response/node_modules/mimic-response": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "license": "MIT", "engines": { "node": ">=10" @@ -14541,10 +13517,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-eql": { + "version": "4.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-extend": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "license": "MIT", "engines": { "node": ">=4.0.0" @@ -14552,8 +13537,6 @@ }, "node_modules/deepmerge": { "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14561,8 +13544,6 @@ }, "node_modules/default-browser": { "version": "5.5.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", - "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", "license": "MIT", "dependencies": { "bundle-name": "^4.1.0", @@ -14577,8 +13558,6 @@ }, "node_modules/default-browser-id": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "license": "MIT", "engines": { "node": ">=18" @@ -14589,8 +13568,6 @@ }, "node_modules/defer-to-connect": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", "license": "MIT", "engines": { "node": ">=10" @@ -14598,8 +13575,6 @@ }, "node_modules/define-data-property": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -14615,8 +13590,6 @@ }, "node_modules/define-lazy-prop": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "license": "MIT", "engines": { "node": ">=8" @@ -14624,8 +13597,6 @@ }, "node_modules/define-properties": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", @@ -14641,8 +13612,6 @@ }, "node_modules/delaunator": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", - "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", "license": "ISC", "dependencies": { "robust-predicates": "^3.0.2" @@ -14650,8 +13619,6 @@ }, "node_modules/delay": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", - "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", "license": "MIT", "engines": { "node": ">=10" @@ -14662,8 +13629,6 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", "engines": { "node": ">=0.4.0" @@ -14671,8 +13636,6 @@ }, "node_modules/depd": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -14680,8 +13643,6 @@ }, "node_modules/dequal": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "license": "MIT", "engines": { "node": ">=6" @@ -14689,18 +13650,22 @@ }, "node_modules/destroy": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-indent": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-libc": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "engines": { "node": ">=8" @@ -14708,14 +13673,10 @@ }, "node_modules/detect-node": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "license": "MIT" }, "node_modules/detect-package-manager": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/detect-package-manager/-/detect-package-manager-3.0.2.tgz", - "integrity": "sha512-8JFjJHutStYrfWwzfretQoyNGoZVW1Fsrp4JO9spa7h/fBfwgTMEIy4/LBzRDGsxwVPHU0q+T9YvwLDJoOApLQ==", "license": "MIT", "dependencies": { "execa": "^5.1.1" @@ -14726,8 +13687,6 @@ }, "node_modules/detect-port": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-2.1.0.tgz", - "integrity": "sha512-epZuWb/6Q62L+nDHJc/hQAqf8pylsqgk3BpZXVBx1CDnr3nkrVNn73Uu1rXcFzkNcc+hkP3whuOg7JZYaQB65Q==", "license": "MIT", "dependencies": { "address": "^2.0.1" @@ -14742,8 +13701,6 @@ }, "node_modules/devlop": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", "license": "MIT", "dependencies": { "dequal": "^2.0.0" @@ -14755,14 +13712,10 @@ }, "node_modules/didyoumean": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "license": "Apache-2.0" }, "node_modules/dir-glob": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "license": "MIT", "dependencies": { "path-type": "^4.0.0" @@ -14773,14 +13726,10 @@ }, "node_modules/dlv": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, "node_modules/dns-packet": { "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "license": "MIT", "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" @@ -14791,8 +13740,6 @@ }, "node_modules/docusaurus-plugin-openapi-docs": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/docusaurus-plugin-openapi-docs/-/docusaurus-plugin-openapi-docs-5.2.0.tgz", - "integrity": "sha512-MjrfRAMB64uvdxRVz6L9AXWe4QFjCdoBAzYs306yyI3nnXHsFj2lv2FnLA90JV9CAUZaGiYMvvkzBo2Nrkq/9w==", "license": "MIT", "dependencies": { "@apidevtools/json-schema-ref-parser": "^15.3.3", @@ -14823,8 +13770,6 @@ }, "node_modules/docusaurus-plugin-openapi-docs/node_modules/chalk": { "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -14834,9 +13779,7 @@ } }, "node_modules/docusaurus-plugin-sass": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/docusaurus-plugin-sass/-/docusaurus-plugin-sass-0.2.7.tgz", - "integrity": "sha512-v+XWW2BBlKiBfqNbDr8iF/DUcWIXDTsAVAfkbG0/pzCx9yV0zK6Qu0ZVFxpZXcMihokzl0zFD1HIN/M4TnzEwA==", + "version": "0.2.6", "license": "MIT", "peer": true, "dependencies": { @@ -14849,8 +13792,6 @@ }, "node_modules/docusaurus-plugin-typedoc": { "version": "1.4.2", - "resolved": "https://registry.npmjs.org/docusaurus-plugin-typedoc/-/docusaurus-plugin-typedoc-1.4.2.tgz", - "integrity": "sha512-1qerRejLSYxEWdyVPLDMMeKFPLA/37yZAsdwJy9ThHFQR78+v3b5spSbk67VHGLr2mAn4FVHu0aGJ6p7iWotSg==", "dev": true, "license": "MIT", "dependencies": { @@ -14862,8 +13803,6 @@ }, "node_modules/docusaurus-theme-openapi-docs": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/docusaurus-theme-openapi-docs/-/docusaurus-theme-openapi-docs-5.2.0.tgz", - "integrity": "sha512-L0b80LzaMUfr76a9EQXRPCf8nxkEz8Xo6Aknnke1UeE2oXsgoiVki6U+RTE7GmJRjO8zSNKXyckGmGmqqWuHeA==", "license": "MIT", "dependencies": { "@hookform/error-message": "^2.0.1", @@ -14908,8 +13847,6 @@ }, "node_modules/docusaurus-theme-openapi-docs/node_modules/pako": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/pako/-/pako-3.0.1.tgz", - "integrity": "sha512-GupotUUI0mlhugKjUs4bjOwLt3nrehy9Ys2dxC0GtgVef5cnKggkDMmf2bq2poCCuVXopWPmqsc9VDT2iJUy+w==", "funding": [ { "type": "github", @@ -14923,9 +13860,7 @@ "license": "(MIT AND Zlib)" }, "node_modules/docusaurus-theme-openapi-docs/node_modules/sass-loader": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-17.0.1.tgz", - "integrity": "sha512-pgJMwCuLjVTSIWsv/2luVRXKlCeaViUGcgSe8dx95zMG4hUwqIMFmjbhq29ypFLflJU0GyiJZeLM9iI1n3KYAA==", + "version": "17.0.0", "license": "MIT", "engines": { "node": ">= 22.11.0" @@ -14957,8 +13892,6 @@ }, "node_modules/dom-converter": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", "license": "MIT", "dependencies": { "utila": "~0.4" @@ -14966,8 +13899,6 @@ }, "node_modules/dom-serializer": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", @@ -14980,8 +13911,6 @@ }, "node_modules/domelementtype": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", "funding": [ { "type": "github", @@ -14992,8 +13921,6 @@ }, "node_modules/domhandler": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" @@ -15007,8 +13934,6 @@ }, "node_modules/dompurify": { "version": "3.4.14", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.14.tgz", - "integrity": "sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -15016,8 +13941,6 @@ }, "node_modules/domutils": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", @@ -15030,8 +13953,6 @@ }, "node_modules/dot-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "license": "MIT", "dependencies": { "no-case": "^3.0.4", @@ -15040,8 +13961,6 @@ }, "node_modules/dot-prop": { "version": "9.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", - "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", "license": "MIT", "dependencies": { "type-fest": "^4.18.2" @@ -15055,8 +13974,6 @@ }, "node_modules/dot-prop/node_modules/type-fest": { "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" @@ -15067,8 +13984,6 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -15081,41 +13996,29 @@ }, "node_modules/duplexer": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "license": "MIT" }, "node_modules/eastasianwidth": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, "node_modules/ee-first": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, "node_modules/eip55": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/eip55/-/eip55-2.1.1.tgz", - "integrity": "sha512-WcagVAmNu2Ww2cDUfzuWVntYwFxbvZ5MvIyLZpMjTTkjD6sCvkGOiS86jTppzu9/gWsc8isLHAeMBWK02OnZmA==", "license": "MIT", "dependencies": { "keccak": "^3.0.3" } }, "node_modules/electron-to-chromium": { - "version": "1.5.420", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz", - "integrity": "sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA==", + "version": "1.5.414", "license": "ISC" }, "node_modules/elliptic": { "version": "6.6.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", - "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", "license": "MIT", "dependencies": { "bn.js": "^4.11.9", @@ -15129,26 +14032,18 @@ }, "node_modules/elliptic/node_modules/bn.js": { "version": "4.12.5", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", - "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", "license": "MIT" }, "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "version": "10.6.0", "license": "MIT" }, "node_modules/emojilib": { "version": "2.4.0", - "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", - "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", "license": "MIT" }, "node_modules/emojis-list": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "license": "MIT", "engines": { "node": ">= 4" @@ -15156,8 +14051,6 @@ }, "node_modules/emoticon": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz", - "integrity": "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==", "license": "MIT", "funding": { "type": "github", @@ -15166,8 +14059,6 @@ }, "node_modules/encodeurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -15175,8 +14066,6 @@ }, "node_modules/encoding-sniffer": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", - "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", "license": "MIT", "dependencies": { "iconv-lite": "^0.6.3", @@ -15188,8 +14077,6 @@ }, "node_modules/encoding-sniffer/node_modules/iconv-lite": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -15200,8 +14087,6 @@ }, "node_modules/end-of-stream": { "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -15209,8 +14094,6 @@ }, "node_modules/enhanced-resolve": { "version": "5.24.5", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", - "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -15220,10 +14103,39 @@ "node": ">=10.13.0" } }, + "node_modules/enquirer": { + "version": "2.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/enquirer/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/enquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/entities": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -15234,8 +14146,6 @@ }, "node_modules/error-ex": { "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -15243,8 +14153,6 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -15252,8 +14160,6 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -15261,14 +14167,10 @@ }, "node_modules/es-module-lexer": { "version": "2.3.2", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", - "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -15279,8 +14181,6 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -15293,9 +14193,7 @@ } }, "node_modules/es-toolkit": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.52.0.tgz", - "integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==", + "version": "1.51.0", "license": "MIT", "workspaces": [ "docs", @@ -15306,14 +14204,10 @@ }, "node_modules/es6-promise": { "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", "license": "MIT" }, "node_modules/es6-promisify": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", "license": "MIT", "dependencies": { "es6-promise": "^4.0.3" @@ -15321,8 +14215,6 @@ }, "node_modules/esast-util-from-estree": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", - "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -15337,8 +14229,6 @@ }, "node_modules/esast-util-from-js": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", - "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -15353,8 +14243,6 @@ }, "node_modules/esbuild": { "version": "0.28.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", - "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -15395,8 +14283,6 @@ }, "node_modules/escalade": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { "node": ">=6" @@ -15404,8 +14290,6 @@ }, "node_modules/escape-goat": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", - "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", "license": "MIT", "engines": { "node": ">=12" @@ -15416,14 +14300,10 @@ }, "node_modules/escape-html": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "license": "MIT", "engines": { "node": ">=10" @@ -15432,10 +14312,70 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/estree-util-attach-comments": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", - "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" @@ -15447,8 +14387,6 @@ }, "node_modules/estree-util-build-jsx": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", - "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -15463,8 +14401,6 @@ }, "node_modules/estree-util-is-identifier-name": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", - "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", "license": "MIT", "funding": { "type": "opencollective", @@ -15473,8 +14409,6 @@ }, "node_modules/estree-util-scope": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.1.tgz", - "integrity": "sha512-B0np3dcdxqILX5e9nEi5/Fr4K7gL4oYFVPV1zRa2e9wRCbQoZZNWOZFYyoInvXUPJXBXjss+QXlWLJChDEHDkA==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -15487,8 +14421,6 @@ }, "node_modules/estree-util-to-js": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", - "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -15502,8 +14434,6 @@ }, "node_modules/estree-util-value-to-estree": { "version": "3.5.0", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.5.0.tgz", - "integrity": "sha512-aMV56R27Gv3QmfmF1MY12GWkGzzeAezAX+UplqHVASfjc9wNzI/X6hC0S9oxq61WT4aQesLGslWP9tKk6ghRZQ==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" @@ -15514,8 +14444,6 @@ }, "node_modules/estree-util-visit": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", - "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -15528,8 +14456,6 @@ }, "node_modules/estree-walker": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" @@ -15537,8 +14463,6 @@ }, "node_modules/esutils": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -15546,8 +14470,6 @@ }, "node_modules/eta": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz", - "integrity": "sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -15558,8 +14480,6 @@ }, "node_modules/etag": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -15567,8 +14487,6 @@ }, "node_modules/ethers": { "version": "6.17.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.17.0.tgz", - "integrity": "sha512-BpyrpIPJ3ydEVow8zGaz1DuPS7YU8DcWxuBnY9a0UA/lvAPwrMr+EPXsfrul628SRaekPNeIM4UFh/91GWZang==", "funding": [ { "type": "individual", @@ -15595,8 +14513,6 @@ }, "node_modules/ethers-abitype": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ethers-abitype/-/ethers-abitype-1.0.3.tgz", - "integrity": "sha512-s5xyGrXhc7LeT9xBvfwOU+q0/8YqyzMaxbgD8aGM5xCXD4zwsPCbbF6gct3TQWaNE49EBA5+MkVxLNonZqVxmg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -15612,8 +14528,6 @@ }, "node_modules/ethers/node_modules/@noble/curves": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", "license": "MIT", "dependencies": { "@noble/hashes": "1.3.2" @@ -15624,8 +14538,6 @@ }, "node_modules/ethers/node_modules/@noble/hashes": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", "license": "MIT", "engines": { "node": ">= 16" @@ -15636,8 +14548,6 @@ }, "node_modules/ethers/node_modules/@types/node": { "version": "22.7.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", - "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "license": "MIT", "dependencies": { "undici-types": "~6.19.2" @@ -15645,20 +14555,14 @@ }, "node_modules/ethers/node_modules/tslib": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "license": "0BSD" }, "node_modules/ethers/node_modules/undici-types": { "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "license": "MIT" }, "node_modules/eval": { "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.8.tgz", - "integrity": "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==", "dependencies": { "@types/node": "*", "require-like": ">= 0.1.1" @@ -15669,14 +14573,10 @@ }, "node_modules/eventemitter3": { "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, "node_modules/events": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", "engines": { "node": ">=0.8.x" @@ -15684,8 +14584,6 @@ }, "node_modules/execa": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", @@ -15707,20 +14605,14 @@ }, "node_modules/execa/node_modules/signal-exit": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, "node_modules/exenv": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", - "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==", "license": "BSD-3-Clause" }, "node_modules/expand-template": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", "license": "(MIT OR WTFPL)", "engines": { "node": ">=6" @@ -15728,8 +14620,6 @@ }, "node_modules/express": { "version": "4.22.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", - "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -15774,8 +14664,6 @@ }, "node_modules/express/node_modules/content-disposition": { "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -15786,8 +14674,6 @@ }, "node_modules/express/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -15795,20 +14681,14 @@ }, "node_modules/express/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/express/node_modules/path-to-regexp": { "version": "0.1.13", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", - "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, "node_modules/express/node_modules/range-parser": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -15816,14 +14696,10 @@ }, "node_modules/extend": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, "node_modules/extend-shallow": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", "license": "MIT", "dependencies": { "is-extendable": "^0.1.0" @@ -15832,24 +14708,23 @@ "node": ">=0.10.0" } }, + "node_modules/extendable-error": { + "version": "0.1.7", + "dev": true, + "license": "MIT" + }, "node_modules/eyes": { "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", - "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", "engines": { "node": "> 0.1.90" } }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, "node_modules/fast-equals": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.2.tgz", - "integrity": "sha512-Ywe6jodPTWOTL9/k0bV7gdfP8twKL5Y8I8CZ933fAY5gBekICZSUQTbyH6ut2NZCNyB05mSUwAuEqdEIaOOlDQ==", + "version": "5.4.1", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -15857,8 +14732,6 @@ }, "node_modules/fast-glob": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -15873,20 +14746,14 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "license": "MIT" }, "node_modules/fast-safe-stringify": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "license": "MIT" }, "node_modules/fast-stable-stringify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", - "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==", "license": "MIT" }, "node_modules/fast-string-truncated-width": { @@ -15905,15 +14772,13 @@ } }, "node_modules/fast-stringify": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/fast-stringify/-/fast-stringify-4.0.2.tgz", - "integrity": "sha512-flF4k1RFAfbGyObjk+jyl6bJQkiKQdbp4k6i13A8YI89j6b8pw5rYulnrbIWcjvQDRt2Sn+VQBi0YCxylFNqZQ==", + "version": "4.0.1", "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.6.tgz", - "integrity": "sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", "funding": [ { "type": "github", @@ -15935,26 +14800,13 @@ "fast-string-width": "^3.0.2" } }, - "node_modules/fastdom": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastdom/-/fastdom-1.0.12.tgz", - "integrity": "sha512-LB+xjSTEbjHE1cWsxu+tN2Xqr1kpi+V9aADI7sVM5ZMaXyYGPHULQMzpJMYqOTULK/73pUkWVzzObFRBkPr+hg==", - "license": "MIT", - "dependencies": { - "strictdom": "^1.0.1" - } - }, "node_modules/fastestsmallesttextencoderdecoder": { "version": "1.0.22", - "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", - "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", "license": "CC0-1.0", "peer": true }, "node_modules/fastq": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", - "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "version": "1.20.1", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -15962,8 +14814,6 @@ }, "node_modules/fault": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", - "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", "license": "MIT", "dependencies": { "format": "^0.2.0" @@ -15975,8 +14825,6 @@ }, "node_modules/faye-websocket": { "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", "license": "Apache-2.0", "dependencies": { "websocket-driver": ">=0.5.1" @@ -15987,8 +14835,6 @@ }, "node_modules/feed": { "version": "4.2.2", - "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", - "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", "license": "MIT", "dependencies": { "xml-js": "^1.6.11" @@ -15999,8 +14845,6 @@ }, "node_modules/figures": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "dev": true, "license": "MIT", "dependencies": { @@ -16015,8 +14859,6 @@ }, "node_modules/file-loader": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", "license": "MIT", "dependencies": { "loader-utils": "^2.0.0", @@ -16035,9 +14877,8 @@ }, "node_modules/file-loader/node_modules/ajv": { "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -16051,8 +14892,6 @@ }, "node_modules/file-loader/node_modules/ajv-keywords": { "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" @@ -16060,14 +14899,10 @@ }, "node_modules/file-loader/node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "license": "MIT" }, "node_modules/file-loader/node_modules/schema-utils": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.8", @@ -16084,14 +14919,10 @@ }, "node_modules/file-saver": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", - "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", "license": "MIT" }, "node_modules/file-type": { "version": "3.9.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", - "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -16099,14 +14930,10 @@ }, "node_modules/file-uri-to-path": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "license": "MIT" }, "node_modules/fill-range": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -16117,8 +14944,6 @@ }, "node_modules/finalhandler": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -16135,8 +14960,6 @@ }, "node_modules/finalhandler/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -16144,14 +14967,10 @@ }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/find-cache-dir": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", - "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", "license": "MIT", "dependencies": { "common-path-prefix": "^3.0.0", @@ -16166,8 +14985,6 @@ }, "node_modules/find-up": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { @@ -16181,10 +14998,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flat": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "license": "BSD-3-Clause", "bin": { "flat": "cli.js" @@ -16192,8 +15015,6 @@ }, "node_modules/focus-trap": { "version": "8.2.2", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-8.2.2.tgz", - "integrity": "sha512-qV0g8hRYBqgACcFOH3f9wXc4zPKhr/0z9RI2a6ZijZ72EeBi4g8oBy8zAWuUR1TsMpOzwpUMFvjdasrC41Joug==", "license": "MIT", "dependencies": { "tabbable": "^6.5.0" @@ -16201,8 +15022,6 @@ }, "node_modules/focus-trap-react": { "version": "12.0.3", - "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-12.0.3.tgz", - "integrity": "sha512-4eXtzhRTtFrxle9Tkl0Wkj8SFJnWz3i3yb5e53Mdn4E8XZQ/Lzqom9U4uUAJ8wd7yDmyGVP96dxwQZXLXd4sbg==", "license": "MIT", "dependencies": { "focus-trap": "^8.2.2", @@ -16217,8 +15036,6 @@ }, "node_modules/follow-redirects": { "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", @@ -16237,14 +15054,10 @@ }, "node_modules/foreach": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", - "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==", "license": "MIT" }, "node_modules/foreground-child": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { @@ -16260,8 +15073,6 @@ }, "node_modules/form-data": { "version": "4.0.6", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", - "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -16276,8 +15087,6 @@ }, "node_modules/form-data-encoder": { "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", - "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", "license": "MIT", "engines": { "node": ">= 14.17" @@ -16285,16 +15094,12 @@ }, "node_modules/format": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", - "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", "engines": { "node": ">=0.4.x" } }, "node_modules/forwarded": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -16302,8 +15107,6 @@ }, "node_modules/fraction.js": { "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "license": "MIT", "engines": { "node": "*" @@ -16315,8 +15118,6 @@ }, "node_modules/fresh": { "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -16324,14 +15125,10 @@ }, "node_modules/fs-constants": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, "node_modules/fs-extra": { "version": "11.4.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", - "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -16344,15 +15141,10 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -16364,8 +15156,6 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -16373,8 +15163,6 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -16382,8 +15170,6 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -16391,8 +15177,6 @@ }, "node_modules/get-east-asian-width": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "license": "MIT", "engines": { "node": ">=18" @@ -16401,10 +15185,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -16427,14 +15217,10 @@ }, "node_modules/get-own-enumerable-property-symbols": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", "license": "ISC" }, "node_modules/get-port": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.2.0.tgz", - "integrity": "sha512-afP4W205ONCuMoPBqcR6PSXnzX35KTcJygfJfcp+QY+uwm3p20p1YczWXhlICIzGMCxYBQcySEcOgsJcrkyobg==", "dev": true, "license": "MIT", "engines": { @@ -16446,8 +15232,6 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -16459,8 +15243,6 @@ }, "node_modules/get-stream": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "license": "MIT", "engines": { "node": ">=10" @@ -16471,32 +15253,23 @@ }, "node_modules/github-from-package": { "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "license": "MIT" }, "node_modules/github-slugger": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", - "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", "license": "ISC" }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", + "version": "13.0.6", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -16504,8 +15277,6 @@ }, "node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -16516,8 +15287,6 @@ }, "node_modules/glob-to-regex.js": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", - "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", "license": "Apache-2.0", "engines": { "node": ">=10.0" @@ -16532,8 +15301,6 @@ }, "node_modules/global-directory": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", - "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", "license": "MIT", "dependencies": { "ini": "4.1.1" @@ -16547,8 +15314,6 @@ }, "node_modules/global-directory/node_modules/ini": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -16556,8 +15321,6 @@ }, "node_modules/global-dirs": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", "license": "MIT", "dependencies": { "ini": "2.0.0" @@ -16571,17 +15334,24 @@ }, "node_modules/global-dirs/node_modules/ini": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", "license": "ISC", "engines": { "node": ">=10" } }, + "node_modules/globals": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globby": { "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "license": "MIT", "dependencies": { "array-union": "^2.1.0", @@ -16600,8 +15370,6 @@ }, "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -16674,8 +15442,6 @@ }, "node_modules/gql.tada": { "version": "1.11.3", - "resolved": "https://registry.npmjs.org/gql.tada/-/gql.tada-1.11.3.tgz", - "integrity": "sha512-5JCI4j2f0nug8ILaCQys/yjOP78QqqjVUf47OQsME63rZfemsHT3e5vcfbHsnZiG6vxyqpKUJArtXEVwSefUhw==", "license": "MIT", "dependencies": { "@0no-co/graphql.web": "^1.3.2", @@ -16693,14 +15459,10 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, "node_modules/graphlib": { "version": "2.1.8", - "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", - "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", "license": "MIT", "dependencies": { "lodash": "^4.17.15" @@ -16708,17 +15470,14 @@ }, "node_modules/graphql": { "version": "17.0.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-17.0.2.tgz", - "integrity": "sha512-FRWbddMxfkjiB7z+aQDWIR+E34xo9I8c9mtK2RPv8PmMzKRvrdsreHL/Ui/TmwHJfhHChEtsFPyMHKI+xuarQQ==", "license": "MIT", + "peer": true, "engines": { "node": "^22.0.0 || ^24.0.0 || ^25.0.0 || >=26.0.0" } }, "node_modules/gzip-size": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", "license": "MIT", "dependencies": { "duplexer": "^0.1.2" @@ -16732,20 +15491,14 @@ }, "node_modules/hachure-fill": { "version": "0.5.2", - "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", - "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", "license": "MIT" }, "node_modules/handle-thing": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "license": "MIT" }, "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "license": "MIT", "engines": { "node": ">=8" @@ -16753,8 +15506,6 @@ }, "node_modules/has-property-descriptors": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -16765,8 +15516,6 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -16777,8 +15526,6 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -16792,8 +15539,6 @@ }, "node_modules/has-yarn": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", - "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -16804,8 +15549,6 @@ }, "node_modules/hash.js": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -16814,8 +15557,6 @@ }, "node_modules/hasown": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -16826,8 +15567,6 @@ }, "node_modules/hast-util-from-parse5": { "version": "8.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", - "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -16846,8 +15585,6 @@ }, "node_modules/hast-util-parse-selector": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", - "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" @@ -16859,8 +15596,6 @@ }, "node_modules/hast-util-raw": { "version": "9.1.0", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", - "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -16884,8 +15619,6 @@ }, "node_modules/hast-util-to-estree": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", - "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -16912,8 +15645,6 @@ }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", - "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -16939,8 +15670,6 @@ }, "node_modules/hast-util-to-parse5": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", - "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -16958,8 +15687,6 @@ }, "node_modules/hast-util-whitespace": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" @@ -16971,8 +15698,6 @@ }, "node_modules/hastscript": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", - "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -16988,8 +15713,6 @@ }, "node_modules/he": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "license": "MIT", "bin": { "he": "bin/he" @@ -16997,8 +15720,6 @@ }, "node_modules/history": { "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.1.2", @@ -17011,8 +15732,6 @@ }, "node_modules/hmac-drbg": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", "license": "MIT", "dependencies": { "hash.js": "^1.0.3", @@ -17022,8 +15741,6 @@ }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", "license": "BSD-3-Clause", "dependencies": { "react-is": "^16.7.0" @@ -17031,8 +15748,6 @@ }, "node_modules/hpack.js": { "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", "license": "MIT", "dependencies": { "inherits": "^2.0.1", @@ -17043,14 +15758,10 @@ }, "node_modules/hpack.js/node_modules/isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, "node_modules/hpack.js/node_modules/readable-stream": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -17064,14 +15775,10 @@ }, "node_modules/hpack.js/node_modules/safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, "node_modules/hpack.js/node_modules/string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" @@ -17079,14 +15786,10 @@ }, "node_modules/html-escaper": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "license": "MIT" }, "node_modules/html-minifier-terser": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", - "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", "license": "MIT", "dependencies": { "camel-case": "^4.1.2", @@ -17106,8 +15809,6 @@ }, "node_modules/html-minifier-terser/node_modules/commander": { "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "license": "MIT", "engines": { "node": ">=14" @@ -17115,8 +15816,6 @@ }, "node_modules/html-tags": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", - "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", "license": "MIT", "engines": { "node": ">=8" @@ -17127,8 +15826,6 @@ }, "node_modules/html-url-attributes": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", - "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", "license": "MIT", "funding": { "type": "opencollective", @@ -17137,8 +15834,6 @@ }, "node_modules/html-void-elements": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", "license": "MIT", "funding": { "type": "github", @@ -17147,8 +15842,6 @@ }, "node_modules/html-webpack-plugin": { "version": "5.6.8", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.8.tgz", - "integrity": "sha512-MZmKQcTnhEh1SPSyMiEytIeDZDUoBZVorNHivQGXMASHf/BSGGOrKa2xQ5bGx3TCe1n109ecCt+cpww7wwWhKA==", "license": "MIT", "dependencies": { "@types/html-minifier-terser": "^6.0.0", @@ -17179,8 +15872,6 @@ }, "node_modules/html-webpack-plugin/node_modules/commander": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "license": "MIT", "engines": { "node": ">= 12" @@ -17188,8 +15879,6 @@ }, "node_modules/html-webpack-plugin/node_modules/html-minifier-terser": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", "license": "MIT", "dependencies": { "camel-case": "^4.1.2", @@ -17209,8 +15898,6 @@ }, "node_modules/htmlparser2": { "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -17228,20 +15915,14 @@ }, "node_modules/http-cache-semantics": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "license": "BSD-2-Clause" }, "node_modules/http-deceiver": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", "license": "MIT" }, "node_modules/http-errors": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { "depd": "~2.0.0", @@ -17260,14 +15941,10 @@ }, "node_modules/http-parser-js": { "version": "0.5.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", "license": "MIT" }, "node_modules/http-proxy": { "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "license": "MIT", "dependencies": { "eventemitter3": "^4.0.0", @@ -17280,8 +15957,6 @@ }, "node_modules/http-proxy-middleware": { "version": "2.0.10", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz", - "integrity": "sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==", "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.8", @@ -17304,8 +15979,6 @@ }, "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", "license": "MIT", "engines": { "node": ">=10" @@ -17316,20 +15989,14 @@ }, "node_modules/http-proxy/node_modules/eventemitter3": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, "node_modules/http-reasons": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/http-reasons/-/http-reasons-0.1.0.tgz", - "integrity": "sha512-P6kYh0lKZ+y29T2Gqz+RlC9WBLhKe8kDmcJ+A+611jFfxdPsbMRQ5aNmFRM3lENqFkK+HTTL+tlQviAiv0AbLQ==", "license": "Apache-2.0" }, "node_modules/http2-client": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", - "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", "license": "MIT" }, "node_modules/http2-wrapper": { @@ -17347,8 +16014,6 @@ }, "node_modules/https-proxy-agent": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", "dependencies": { "agent-base": "6", @@ -17358,10 +16023,16 @@ "node": ">= 6" } }, + "node_modules/human-id": { + "version": "4.2.1", + "dev": true, + "license": "MIT", + "bin": { + "human-id": "dist/cli.js" + } + }, "node_modules/human-signals": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "license": "Apache-2.0", "engines": { "node": ">=10.17.0" @@ -17369,8 +16040,6 @@ }, "node_modules/humanize-ms": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "license": "MIT", "dependencies": { "ms": "^2.0.0" @@ -17378,8 +16047,6 @@ }, "node_modules/hyperdyperid": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", - "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", "license": "MIT", "engines": { "node": ">=10.18" @@ -17387,8 +16054,6 @@ }, "node_modules/iconv-lite": { "version": "0.7.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -17403,8 +16068,6 @@ }, "node_modules/icss-utils": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" @@ -17415,8 +16078,6 @@ }, "node_modules/ieee754": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "funding": [ { "type": "github", @@ -17435,8 +16096,6 @@ }, "node_modules/ignore": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "license": "MIT", "engines": { "node": ">= 4" @@ -17467,14 +16126,10 @@ }, "node_modules/immediate": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", - "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==", "license": "MIT" }, "node_modules/immer": { "version": "11.1.18", - "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.18.tgz", - "integrity": "sha512-EQyQtLiYW029lyoczMl/Hh4Xu7cDecSc58JRYpHyL4tIAu3eqd1yJzQX04d2BZHDkzFFvm6qJEJWOtfDSWAXbQ==", "license": "MIT", "funding": { "type": "opencollective", @@ -17483,14 +16138,10 @@ }, "node_modules/immutable": { "version": "5.1.9", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", - "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", "license": "MIT" }, "node_modules/import-fresh": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -17505,8 +16156,6 @@ }, "node_modules/import-lazy": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", "license": "MIT", "engines": { "node": ">=8" @@ -17514,8 +16163,6 @@ }, "node_modules/import-meta-resolve": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", "license": "MIT", "funding": { "type": "github", @@ -17524,8 +16171,6 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "license": "MIT", "engines": { "node": ">=0.8.19" @@ -17533,8 +16178,6 @@ }, "node_modules/indent-string": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "license": "MIT", "engines": { "node": ">=8" @@ -17542,8 +16185,6 @@ }, "node_modules/infima": { "version": "0.2.0-alpha.45", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz", - "integrity": "sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==", "license": "MIT", "engines": { "node": ">=12" @@ -17551,9 +16192,6 @@ }, "node_modules/inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -17562,26 +16200,18 @@ }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, "node_modules/inline-style-parser": { "version": "0.2.7", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", - "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", "license": "MIT" }, "node_modules/internmap": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", "license": "ISC", "engines": { "node": ">=12" @@ -17589,8 +16219,6 @@ }, "node_modules/interpret": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -17598,8 +16226,6 @@ }, "node_modules/invariant": { "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "license": "MIT", "dependencies": { "loose-envify": "^1.0.0" @@ -17607,8 +16233,6 @@ }, "node_modules/ipaddr.js": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz", - "integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==", "license": "MIT", "engines": { "node": ">= 10" @@ -17616,8 +16240,6 @@ }, "node_modules/is-alphabetical": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", "license": "MIT", "funding": { "type": "github", @@ -17626,8 +16248,6 @@ }, "node_modules/is-alphanumerical": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", "license": "MIT", "dependencies": { "is-alphabetical": "^2.0.0", @@ -17640,14 +16260,10 @@ }, "node_modules/is-arrayish": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "license": "MIT" }, "node_modules/is-binary-path": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -17658,8 +16274,6 @@ }, "node_modules/is-ci": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", "license": "MIT", "dependencies": { "ci-info": "^3.2.0" @@ -17670,8 +16284,6 @@ }, "node_modules/is-core-module": { "version": "2.16.2", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", - "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "license": "MIT", "dependencies": { "hasown": "^2.0.3" @@ -17685,8 +16297,6 @@ }, "node_modules/is-decimal": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", "license": "MIT", "funding": { "type": "github", @@ -17695,8 +16305,6 @@ }, "node_modules/is-docker": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "license": "MIT", "bin": { "is-docker": "cli.js" @@ -17710,8 +16318,6 @@ }, "node_modules/is-extendable": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -17719,8 +16325,6 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -17728,8 +16332,6 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", "engines": { "node": ">=8" @@ -17737,8 +16339,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -17749,8 +16349,6 @@ }, "node_modules/is-hexadecimal": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", - "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", "license": "MIT", "funding": { "type": "github", @@ -17759,8 +16357,6 @@ }, "node_modules/is-in-ci": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz", - "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==", "license": "MIT", "bin": { "is-in-ci": "cli.js" @@ -17774,8 +16370,6 @@ }, "node_modules/is-inside-container": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "license": "MIT", "dependencies": { "is-docker": "^3.0.0" @@ -17792,8 +16386,6 @@ }, "node_modules/is-inside-container/node_modules/is-docker": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "license": "MIT", "bin": { "is-docker": "cli.js" @@ -17807,8 +16399,6 @@ }, "node_modules/is-installed-globally": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.0.tgz", - "integrity": "sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==", "license": "MIT", "dependencies": { "global-directory": "^4.0.1", @@ -17823,8 +16413,6 @@ }, "node_modules/is-network-error": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", - "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", "license": "MIT", "engines": { "node": ">=16" @@ -17835,8 +16423,6 @@ }, "node_modules/is-npm": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.1.0.tgz", - "integrity": "sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==", "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -17847,8 +16433,6 @@ }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "license": "MIT", "engines": { "node": ">=0.12.0" @@ -17856,8 +16440,6 @@ }, "node_modules/is-obj": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -17865,8 +16447,6 @@ }, "node_modules/is-path-inside": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", - "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", "license": "MIT", "engines": { "node": ">=12" @@ -17877,8 +16457,6 @@ }, "node_modules/is-plain-obj": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "license": "MIT", "engines": { "node": ">=12" @@ -17889,8 +16467,6 @@ }, "node_modules/is-plain-object": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "license": "MIT", "dependencies": { "isobject": "^3.0.1" @@ -17901,8 +16477,6 @@ }, "node_modules/is-regexp": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -17910,8 +16484,6 @@ }, "node_modules/is-stream": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", "engines": { "node": ">=8" @@ -17920,16 +16492,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-subdir": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "better-path-resolve": "1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/is-typedarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "license": "MIT" }, "node_modules/is-unicode-supported": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, "license": "MIT", "engines": { @@ -17939,10 +16518,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-windows": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-wsl": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "license": "MIT", "dependencies": { "is-docker": "^2.0.0" @@ -17953,8 +16538,6 @@ }, "node_modules/is-yarn-global": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", - "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", "license": "MIT", "engines": { "node": ">=12" @@ -17962,20 +16545,14 @@ }, "node_modules/isarray": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, "node_modules/isobject": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -17983,8 +16560,6 @@ }, "node_modules/isomorphic-ws": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", "license": "MIT", "peerDependencies": { "ws": "*" @@ -17992,8 +16567,6 @@ }, "node_modules/isows": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", - "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", "dev": true, "funding": [ { @@ -18008,8 +16581,6 @@ }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -18018,8 +16589,6 @@ }, "node_modules/istanbul-lib-report": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -18033,8 +16602,6 @@ }, "node_modules/istanbul-reports": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -18047,8 +16614,6 @@ }, "node_modules/jayson": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.3.0.tgz", - "integrity": "sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ==", "license": "MIT", "dependencies": { "@types/connect": "^3.4.33", @@ -18073,20 +16638,14 @@ }, "node_modules/jayson/node_modules/@types/node": { "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", "license": "MIT" }, "node_modules/jayson/node_modules/commander": { "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, "node_modules/jest-util": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -18102,8 +16661,6 @@ }, "node_modules/jest-worker": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -18117,8 +16674,6 @@ }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -18132,8 +16687,6 @@ }, "node_modules/jiti": { "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -18141,8 +16694,6 @@ }, "node_modules/joi": { "version": "18.2.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-18.2.3.tgz", - "integrity": "sha512-N5A3KTWQpPWT4ExxxPlUx7WmykGXRzhNidWhV41d6Abu9YfI2NyWCJuxdPnslJCPWtbRpSVOWSnSS6GakLM/Rg==", "license": "BSD-3-Clause", "dependencies": { "@hapi/address": "^5.1.1", @@ -18159,8 +16710,6 @@ }, "node_modules/js-levenshtein": { "version": "1.1.6", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -18168,20 +16717,14 @@ }, "node_modules/js-sha3": { "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", "license": "MIT" }, "node_modules/js-tokens": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", - "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "version": "4.3.1", "funding": [ { "type": "github", @@ -18202,8 +16745,6 @@ }, "node_modules/jsesc": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -18214,14 +16755,10 @@ }, "node_modules/json-buffer": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "license": "MIT" }, "node_modules/json-crawl": { "version": "0.5.3", - "resolved": "https://registry.npmjs.org/json-crawl/-/json-crawl-0.5.3.tgz", - "integrity": "sha512-BEjjCw8c7SxzNK4orhlWD5cXQh8vCk2LqDr4WgQq4CV+5dvopeYwt1Tskg67SuSLKvoFH5g0yuYtg7rcfKV6YA==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -18229,14 +16766,10 @@ }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "license": "MIT" }, "node_modules/json-pointer": { "version": "0.6.2", - "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", - "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", "license": "MIT", "dependencies": { "foreach": "^2.0.4" @@ -18244,8 +16777,6 @@ }, "node_modules/json-schema-compare": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz", - "integrity": "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==", "license": "MIT", "dependencies": { "lodash": "^4.17.4" @@ -18253,8 +16784,6 @@ }, "node_modules/json-schema-merge-allof": { "version": "0.8.1", - "resolved": "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz", - "integrity": "sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w==", "license": "MIT", "dependencies": { "compute-lcm": "^1.1.2", @@ -18267,8 +16796,6 @@ }, "node_modules/json-schema-to-ts": { "version": "2.7.2", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-2.7.2.tgz", - "integrity": "sha512-R1JfqKqbBR4qE8UyBR56Ms30LL62/nlhoz+1UkfI/VE7p54Awu919FZ6ZUPG8zIa3XB65usPJgr1ONVncUGSaQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", @@ -18281,20 +16808,14 @@ }, "node_modules/json-schema-traverse": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, "node_modules/json-stringify-safe": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "license": "ISC" }, "node_modules/json5": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -18305,8 +16826,6 @@ }, "node_modules/jsonfile": { "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -18317,8 +16836,6 @@ }, "node_modules/jssha": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.2.0.tgz", - "integrity": "sha512-QuruyBENDWdN4tZwJbQq7/eAK85FqrI4oDbXjy5IBhYD+2pTJyBUWZe8ctWaCkrV0gy6AaelgOZZBMeswEa/6Q==", "license": "BSD-3-Clause", "engines": { "node": "*" @@ -18326,8 +16843,6 @@ }, "node_modules/jwt-decode": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", - "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", "license": "MIT", "engines": { "node": ">=18" @@ -18335,8 +16850,6 @@ }, "node_modules/katex": { "version": "0.16.47", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", - "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" @@ -18351,8 +16864,6 @@ }, "node_modules/katex/node_modules/commander": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "license": "MIT", "engines": { "node": ">= 12" @@ -18360,8 +16871,6 @@ }, "node_modules/keccak": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", - "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -18375,22 +16884,16 @@ }, "node_modules/keyv": { "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } }, "node_modules/khroma": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", - "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + "version": "2.1.0" }, "node_modules/kind-of": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -18398,8 +16901,6 @@ }, "node_modules/klaw-sync": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", - "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", "license": "MIT", "dependencies": { "graceful-fs": "^4.1.11" @@ -18407,8 +16908,6 @@ }, "node_modules/kleur": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "license": "MIT", "engines": { "node": ">=6" @@ -18416,8 +16915,6 @@ }, "node_modules/ky": { "version": "1.14.3", - "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.3.tgz", - "integrity": "sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==", "license": "MIT", "engines": { "node": ">=18" @@ -18428,8 +16925,6 @@ }, "node_modules/latest-version": { "version": "9.0.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-9.0.0.tgz", - "integrity": "sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==", "license": "MIT", "dependencies": { "package-json": "^10.0.0" @@ -18443,8 +16938,6 @@ }, "node_modules/launch-editor": { "version": "2.14.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", - "integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==", "license": "MIT", "dependencies": { "picocolors": "^1.1.1", @@ -18453,14 +16946,10 @@ }, "node_modules/layout-base": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", - "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", "license": "MIT" }, "node_modules/leven": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "license": "MIT", "engines": { "node": ">=6" @@ -18468,8 +16957,6 @@ }, "node_modules/lilconfig": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "license": "MIT", "engines": { "node": ">=14" @@ -18480,14 +16967,10 @@ }, "node_modules/lines-and-columns": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, "node_modules/linkify-it": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", - "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", "dev": true, "funding": [ { @@ -18506,8 +16989,6 @@ }, "node_modules/liquid-json": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/liquid-json/-/liquid-json-0.3.1.tgz", - "integrity": "sha512-wUayTU8MS827Dam6MxgD72Ui+KOSF+u/eIqpatOtjnvgJ0+mnDq33uC2M7J0tPK+upe/DpUAuK4JUU89iBoNKQ==", "license": "Apache-2.0", "engines": { "node": ">=4" @@ -18515,8 +16996,6 @@ }, "node_modules/loader-utils": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "license": "MIT", "dependencies": { "big.js": "^5.2.2", @@ -18529,8 +17008,6 @@ }, "node_modules/locate-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { @@ -18545,38 +17022,31 @@ }, "node_modules/lodash": { "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash-es": { "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", - "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, "node_modules/lodash.debounce": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "license": "MIT" + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "dev": true, "license": "MIT" }, "node_modules/lodash.uniq": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "license": "MIT" }, "node_modules/longest-streak": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", "license": "MIT", "funding": { "type": "github", @@ -18585,8 +17055,6 @@ }, "node_modules/loose-envify": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -18595,10 +17063,16 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "2.3.7", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, "node_modules/lower-case": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", "license": "MIT", "dependencies": { "tslib": "^2.0.3" @@ -18617,30 +17091,23 @@ } }, "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" + "version": "11.5.2", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" } }, "node_modules/lunr": { "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", "license": "MIT" }, "node_modules/lunr-languages": { "version": "1.21.0", - "resolved": "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.21.0.tgz", - "integrity": "sha512-Hj0VwJP1FGwZzVMMZdDtWY2GB2VVKtvVElYtVajzCZCDr2RTucyLpPibrOPGgTlcq2ENQy2gYLtPnzyFu9jZCg==", "license": "MPL-1.1" }, "node_modules/make-dir": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { @@ -18655,14 +17122,10 @@ }, "node_modules/mark.js": { "version": "8.11.1", - "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", - "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", "license": "MIT" }, "node_modules/markdown-extensions": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", - "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", "license": "MIT", "engines": { "node": ">=16" @@ -18672,9 +17135,7 @@ } }, "node_modules/markdown-it": { - "version": "14.3.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.1.tgz", - "integrity": "sha512-4Ej49aYTDFIQ+uBkfX8GBvJGccoARxxPep+7aWTs55ozbjQJpW9M26Fe53vnGgvLeVzva/amzjQQaQu9w0vMhA==", + "version": "14.3.0", "dev": true, "funding": [ { @@ -18701,8 +17162,6 @@ }, "node_modules/markdown-table": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", "license": "MIT", "funding": { "type": "github", @@ -18711,8 +17170,6 @@ }, "node_modules/marked": { "version": "16.4.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", - "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -18723,8 +17180,6 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -18732,8 +17187,6 @@ }, "node_modules/mdast-util-directive": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", - "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -18753,8 +17206,6 @@ }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -18769,8 +17220,6 @@ }, "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "license": "MIT", "engines": { "node": ">=12" @@ -18781,8 +17230,6 @@ }, "node_modules/mdast-util-from-markdown": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", - "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -18805,8 +17252,6 @@ }, "node_modules/mdast-util-from-markdown/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -18821,8 +17266,6 @@ }, "node_modules/mdast-util-frontmatter": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", - "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -18839,8 +17282,6 @@ }, "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "license": "MIT", "engines": { "node": ">=12" @@ -18851,8 +17292,6 @@ }, "node_modules/mdast-util-gfm": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", "license": "MIT", "dependencies": { "mdast-util-from-markdown": "^2.0.0", @@ -18870,8 +17309,6 @@ }, "node_modules/mdast-util-gfm-autolink-literal": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -18887,8 +17324,6 @@ }, "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -18907,8 +17342,6 @@ }, "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -18923,8 +17356,6 @@ }, "node_modules/mdast-util-gfm-footnote": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -18940,8 +17371,6 @@ }, "node_modules/mdast-util-gfm-strikethrough": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -18955,8 +17384,6 @@ }, "node_modules/mdast-util-gfm-table": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -18972,8 +17399,6 @@ }, "node_modules/mdast-util-gfm-task-list-item": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -18988,8 +17413,6 @@ }, "node_modules/mdast-util-mdx": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", - "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", "license": "MIT", "dependencies": { "mdast-util-from-markdown": "^2.0.0", @@ -19005,8 +17428,6 @@ }, "node_modules/mdast-util-mdx-expression": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", - "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -19023,8 +17444,6 @@ }, "node_modules/mdast-util-mdx-jsx": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", - "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -19047,8 +17466,6 @@ }, "node_modules/mdast-util-mdxjs-esm": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", - "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -19065,8 +17482,6 @@ }, "node_modules/mdast-util-phrasing": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -19079,8 +17494,6 @@ }, "node_modules/mdast-util-to-hast": { "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -19100,8 +17513,6 @@ }, "node_modules/mdast-util-to-markdown": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -19121,8 +17532,6 @@ }, "node_modules/mdast-util-to-string": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0" @@ -19134,40 +17543,32 @@ }, "node_modules/mdn-data": { "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "license": "CC0-1.0" }, "node_modules/mdurl": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.1.0.tgz", - "integrity": "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==", "dev": true, "license": "MIT" }, "node_modules/media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/memfs": { - "version": "4.69.1", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.69.1.tgz", - "integrity": "sha512-x4Ion23tLmo/6CTNYrKriwACRWlsw5hg+ZDYr4sRriYiZEu04UmK+7ZE/jXjAeDfe2DlL4UIFAVKllqZlWCJ+g==", + "version": "4.68.1", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.69.1", - "@jsonjoy.com/fs-fsa": "4.69.1", - "@jsonjoy.com/fs-node": "4.69.1", - "@jsonjoy.com/fs-node-builtins": "4.69.1", - "@jsonjoy.com/fs-node-to-fsa": "4.69.1", - "@jsonjoy.com/fs-node-utils": "4.69.1", - "@jsonjoy.com/fs-print": "4.69.1", - "@jsonjoy.com/fs-snapshot": "4.69.1", + "@jsonjoy.com/fs-core": "4.68.1", + "@jsonjoy.com/fs-fsa": "4.68.1", + "@jsonjoy.com/fs-node": "4.68.1", + "@jsonjoy.com/fs-node-builtins": "4.68.1", + "@jsonjoy.com/fs-node-to-fsa": "4.68.1", + "@jsonjoy.com/fs-node-utils": "4.68.1", + "@jsonjoy.com/fs-print": "4.68.1", + "@jsonjoy.com/fs-snapshot": "4.68.1", "@jsonjoy.com/json-pack": "^1.11.0", "@jsonjoy.com/util": "^1.9.0", "glob-to-regex.js": "^1.0.1", @@ -19182,8 +17583,6 @@ }, "node_modules/merge-descriptors": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -19191,41 +17590,34 @@ }, "node_modules/merge-stream": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/mermaid": { - "version": "11.17.2", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.17.2.tgz", - "integrity": "sha512-V6K3C8EBdEsPFZXSKMJe6ppQOENxuHARr9GvHX4hh47lAbhMRD9qf4oEK7LoaRQxULMa80/qt5gHO73aCleBBg==", + "version": "11.16.1", "license": "MIT", "dependencies": { "@braintree/sanitize-url": "^7.1.2", "@iconify/utils": "^3.0.2", - "@mermaid-js/parser": "^1.2.1", + "@mermaid-js/parser": "^1.2.0", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", - "cytoscape": "^3.34.0", + "cytoscape": "^3.33.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", - "dayjs": "^1.11.21", + "dayjs": "^1.11.20", "dompurify": "^3.3.3", "es-toolkit": "^1.45.1", - "fastdom": "1.0.12", - "katex": "^0.16.47", + "katex": "^0.16.45", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", @@ -19236,17 +17628,13 @@ }, "node_modules/methods": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/micro-memoize": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/micro-memoize/-/micro-memoize-5.2.0.tgz", - "integrity": "sha512-r9HPHhVUAbjFxOhf5dP/lK5fRRkkKATXMuTBUR/YInx/CPmmrfo9hKHvXq1+iU+zrR1cORT25g3yWlLfEZsmqQ==", + "version": "5.1.2", "license": "MIT", "dependencies": { "fast-equals": "^5.4.0", @@ -19255,8 +17643,6 @@ }, "node_modules/micromark": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", "funding": [ { "type": "GitHub Sponsors", @@ -19290,8 +17676,6 @@ }, "node_modules/micromark-core-commonmark": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", "funding": [ { "type": "GitHub Sponsors", @@ -19324,8 +17708,6 @@ }, "node_modules/micromark-core-commonmark/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -19344,8 +17726,6 @@ }, "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19364,8 +17744,6 @@ }, "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19380,8 +17758,6 @@ }, "node_modules/micromark-extension-directive": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", - "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -19399,8 +17775,6 @@ }, "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -19419,8 +17793,6 @@ }, "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19439,8 +17811,6 @@ }, "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19455,8 +17825,6 @@ }, "node_modules/micromark-extension-frontmatter": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", - "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", "license": "MIT", "dependencies": { "fault": "^2.0.0", @@ -19471,8 +17839,6 @@ }, "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19491,8 +17857,6 @@ }, "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19507,8 +17871,6 @@ }, "node_modules/micromark-extension-gfm": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", "license": "MIT", "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", @@ -19527,8 +17889,6 @@ }, "node_modules/micromark-extension-gfm-autolink-literal": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", @@ -19543,8 +17903,6 @@ }, "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19563,8 +17921,6 @@ }, "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19579,8 +17935,6 @@ }, "node_modules/micromark-extension-gfm-footnote": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -19599,8 +17953,6 @@ }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -19619,8 +17971,6 @@ }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19639,8 +17989,6 @@ }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19655,8 +18003,6 @@ }, "node_modules/micromark-extension-gfm-strikethrough": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -19673,8 +18019,6 @@ }, "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19689,8 +18033,6 @@ }, "node_modules/micromark-extension-gfm-table": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -19706,8 +18048,6 @@ }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -19726,8 +18066,6 @@ }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19746,8 +18084,6 @@ }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19762,8 +18098,6 @@ }, "node_modules/micromark-extension-gfm-tagfilter": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", "license": "MIT", "dependencies": { "micromark-util-types": "^2.0.0" @@ -19775,8 +18109,6 @@ }, "node_modules/micromark-extension-gfm-task-list-item": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -19792,8 +18124,6 @@ }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -19812,8 +18142,6 @@ }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19832,8 +18160,6 @@ }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19848,8 +18174,6 @@ }, "node_modules/micromark-extension-mdx-expression": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", - "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19874,8 +18198,6 @@ }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -19894,8 +18216,6 @@ }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19914,8 +18234,6 @@ }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19930,8 +18248,6 @@ }, "node_modules/micromark-extension-mdx-jsx": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", - "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -19952,8 +18268,6 @@ }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -19972,8 +18286,6 @@ }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -19992,8 +18304,6 @@ }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20008,8 +18318,6 @@ }, "node_modules/micromark-extension-mdx-md": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", - "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", "license": "MIT", "dependencies": { "micromark-util-types": "^2.0.0" @@ -20021,8 +18329,6 @@ }, "node_modules/micromark-extension-mdxjs": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", - "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", "license": "MIT", "dependencies": { "acorn": "^8.0.0", @@ -20041,8 +18347,6 @@ }, "node_modules/micromark-extension-mdxjs-esm": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", - "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -20062,8 +18366,6 @@ }, "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20082,8 +18384,6 @@ }, "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20098,8 +18398,6 @@ }, "node_modules/micromark-factory-destination": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", "funding": [ { "type": "GitHub Sponsors", @@ -20119,8 +18417,6 @@ }, "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20139,8 +18435,6 @@ }, "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20155,8 +18449,6 @@ }, "node_modules/micromark-factory-label": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", "funding": [ { "type": "GitHub Sponsors", @@ -20177,8 +18469,6 @@ }, "node_modules/micromark-factory-label/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20197,8 +18487,6 @@ }, "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20213,8 +18501,6 @@ }, "node_modules/micromark-factory-mdx-expression": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", - "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", "funding": [ { "type": "GitHub Sponsors", @@ -20240,8 +18526,6 @@ }, "node_modules/micromark-factory-mdx-expression/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -20260,8 +18544,6 @@ }, "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20280,8 +18562,6 @@ }, "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20296,8 +18576,6 @@ }, "node_modules/micromark-factory-space": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", - "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", "funding": [ { "type": "GitHub Sponsors", @@ -20316,8 +18594,6 @@ }, "node_modules/micromark-factory-space/node_modules/micromark-util-types": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", "funding": [ { "type": "GitHub Sponsors", @@ -20332,8 +18608,6 @@ }, "node_modules/micromark-factory-title": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", "funding": [ { "type": "GitHub Sponsors", @@ -20354,8 +18628,6 @@ }, "node_modules/micromark-factory-title/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -20374,8 +18646,6 @@ }, "node_modules/micromark-factory-title/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20394,8 +18664,6 @@ }, "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20410,8 +18678,6 @@ }, "node_modules/micromark-factory-whitespace": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", "funding": [ { "type": "GitHub Sponsors", @@ -20432,8 +18698,6 @@ }, "node_modules/micromark-factory-whitespace/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -20452,8 +18716,6 @@ }, "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20472,8 +18734,6 @@ }, "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20488,8 +18748,6 @@ }, "node_modules/micromark-util-character": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", - "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", "funding": [ { "type": "GitHub Sponsors", @@ -20508,8 +18766,6 @@ }, "node_modules/micromark-util-character/node_modules/micromark-util-types": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", "funding": [ { "type": "GitHub Sponsors", @@ -20524,8 +18780,6 @@ }, "node_modules/micromark-util-chunked": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", "funding": [ { "type": "GitHub Sponsors", @@ -20543,8 +18797,6 @@ }, "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20559,8 +18811,6 @@ }, "node_modules/micromark-util-classify-character": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20580,8 +18830,6 @@ }, "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20600,8 +18848,6 @@ }, "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20616,8 +18862,6 @@ }, "node_modules/micromark-util-combine-extensions": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", "funding": [ { "type": "GitHub Sponsors", @@ -20636,8 +18880,6 @@ }, "node_modules/micromark-util-decode-numeric-character-reference": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", "funding": [ { "type": "GitHub Sponsors", @@ -20655,8 +18897,6 @@ }, "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20671,8 +18911,6 @@ }, "node_modules/micromark-util-decode-string": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", "funding": [ { "type": "GitHub Sponsors", @@ -20693,8 +18931,6 @@ }, "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20713,8 +18949,6 @@ }, "node_modules/micromark-util-decode-string/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20729,8 +18963,6 @@ }, "node_modules/micromark-util-encode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", "funding": [ { "type": "GitHub Sponsors", @@ -20745,8 +18977,6 @@ }, "node_modules/micromark-util-events-to-acorn": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", - "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", "funding": [ { "type": "GitHub Sponsors", @@ -20770,8 +19000,6 @@ }, "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20786,8 +19014,6 @@ }, "node_modules/micromark-util-html-tag-name": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", "funding": [ { "type": "GitHub Sponsors", @@ -20802,8 +19028,6 @@ }, "node_modules/micromark-util-normalize-identifier": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20821,8 +19045,6 @@ }, "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20837,8 +19059,6 @@ }, "node_modules/micromark-util-resolve-all": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", "funding": [ { "type": "GitHub Sponsors", @@ -20856,8 +19076,6 @@ }, "node_modules/micromark-util-sanitize-uri": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", "funding": [ { "type": "GitHub Sponsors", @@ -20877,8 +19095,6 @@ }, "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20897,8 +19113,6 @@ }, "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20913,8 +19127,6 @@ }, "node_modules/micromark-util-subtokenize": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", "funding": [ { "type": "GitHub Sponsors", @@ -20935,8 +19147,6 @@ }, "node_modules/micromark-util-subtokenize/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -20951,8 +19161,6 @@ }, "node_modules/micromark-util-symbol": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", - "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", "funding": [ { "type": "GitHub Sponsors", @@ -20967,8 +19175,6 @@ }, "node_modules/micromark-util-types": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", "funding": [ { "type": "GitHub Sponsors", @@ -20983,8 +19189,6 @@ }, "node_modules/micromark/node_modules/micromark-factory-space": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -21003,8 +19207,6 @@ }, "node_modules/micromark/node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -21023,8 +19225,6 @@ }, "node_modules/micromark/node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -21039,8 +19239,6 @@ }, "node_modules/micromatch": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -21052,8 +19250,6 @@ }, "node_modules/mime": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "license": "MIT", "bin": { "mime": "cli.js" @@ -21064,8 +19260,6 @@ }, "node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -21073,8 +19267,6 @@ }, "node_modules/mime-format": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mime-format/-/mime-format-2.0.2.tgz", - "integrity": "sha512-Y5ERWVcyh3sby9Fx2U5F1yatiTFjNsqF5NltihTWI9QgNtr5o3dbCZdcKa1l2wyfhnwwoP9HGNxga7LqZLA6gw==", "license": "Apache-2.0", "dependencies": { "charset": "^1.0.0" @@ -21082,8 +19274,6 @@ }, "node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -21094,8 +19284,6 @@ }, "node_modules/mimic-fn": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "license": "MIT", "engines": { "node": ">=6" @@ -21115,8 +19303,6 @@ }, "node_modules/mini-css-extract-plugin": { "version": "2.10.2", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.2.tgz", - "integrity": "sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg==", "license": "MIT", "dependencies": { "schema-utils": "^4.0.0", @@ -21135,63 +19321,41 @@ }, "node_modules/minimalistic-assert": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "license": "ISC" }, "node_modules/minimalistic-crypto-utils": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", "license": "MIT" }, "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "license": "ISC", + "version": "10.2.6", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.8" }, "engines": { - "node": "*" - } - }, - "node_modules/minimatch/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/minimatch/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/minimizer-webpack-plugin": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.9.0.tgz", - "integrity": "sha512-OASqv+dewv8vjkOBjqMOHvxUaXhtGBLZAXq7JbmYE8TSzBvOswiGq5JcBdc8ypuLL/8YUT1OEyupga6/iqdyTA==", + "version": "5.6.1", "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^4.3.3", - "terser": "^5.51.0" + "schema-utils": "^4.3.0", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -21207,9 +19371,6 @@ "@minify-html/node": { "optional": true }, - "@napi-rs/image": { - "optional": true - }, "@swc/core": { "optional": true }, @@ -21234,21 +19395,12 @@ "html-minifier-terser": { "optional": true }, - "imagemin": { - "optional": true - }, "lightningcss": { "optional": true }, "postcss": { "optional": true }, - "sharp": { - "optional": true - }, - "svgo": { - "optional": true - }, "uglify-js": { "optional": true } @@ -21256,8 +19408,6 @@ }, "node_modules/minimizer-webpack-plugin/node_modules/jest-worker": { "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -21270,8 +19420,6 @@ }, "node_modules/minimizer-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -21285,8 +19433,6 @@ }, "node_modules/minipass": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -21295,8 +19441,6 @@ }, "node_modules/minizlib": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, "license": "MIT", "dependencies": { @@ -21308,14 +19452,18 @@ }, "node_modules/mkdirp-classic": { "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "license": "MIT" }, + "node_modules/mri": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/mrmime": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "license": "MIT", "engines": { "node": ">=10" @@ -21323,14 +19471,10 @@ }, "node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/multicast-dns": { "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", "license": "MIT", "dependencies": { "dns-packet": "^5.2.2", @@ -21342,8 +19486,6 @@ }, "node_modules/mustache": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", "license": "MIT", "bin": { "mustache": "bin/mustache" @@ -21360,8 +19502,6 @@ }, "node_modules/mz": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "license": "MIT", "dependencies": { "any-promise": "^1.0.0", @@ -21371,8 +19511,6 @@ }, "node_modules/nanoid": { "version": "3.3.18", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", - "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "funding": [ { "type": "github", @@ -21389,14 +19527,10 @@ }, "node_modules/napi-build-utils": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -21404,23 +19538,22 @@ }, "node_modules/neo-async": { "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "license": "MIT" }, "node_modules/neotraverse": { "version": "0.6.15", - "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.15.tgz", - "integrity": "sha512-HZpdkco+JeXq0G+WWpMJ4NsX3pqb5O7eR9uGz3FfoFt+LYzU8iRWp49nJtud6hsDoywM8tIrDo3gjgmOqJA8LA==", "license": "MIT", "engines": { "node": ">= 10" } }, + "node_modules/nice-try": { + "version": "1.0.5", + "dev": true, + "license": "MIT" + }, "node_modules/no-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", "license": "MIT", "dependencies": { "lower-case": "^2.0.2", @@ -21428,9 +19561,7 @@ } }, "node_modules/node-abi": { - "version": "3.96.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz", - "integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==", + "version": "3.95.0", "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -21441,14 +19572,10 @@ }, "node_modules/node-addon-api": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", "license": "MIT" }, "node_modules/node-emoji": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", - "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", "license": "MIT", "dependencies": { "@sindresorhus/is": "^4.6.0", @@ -21462,8 +19589,6 @@ }, "node_modules/node-fetch": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" @@ -21482,8 +19607,6 @@ }, "node_modules/node-fetch-h2": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", - "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", "license": "MIT", "dependencies": { "http2-client": "^1.2.5" @@ -21494,8 +19617,6 @@ }, "node_modules/node-gyp-build": { "version": "4.4.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", - "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", "license": "MIT", "bin": { "node-gyp-build": "bin.js", @@ -21505,8 +19626,6 @@ }, "node_modules/node-hid": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/node-hid/-/node-hid-2.1.2.tgz", - "integrity": "sha512-qhCyQqrPpP93F/6Wc/xUR7L8mAJW0Z6R7HMQV8jCHHksAxNDe/4z4Un/H9CpLOT+5K39OPyt9tIQlavxWES3lg==", "hasInstallScript": true, "license": "(MIT OR X11)", "dependencies": { @@ -21523,14 +19642,10 @@ }, "node_modules/node-hid/node_modules/node-addon-api": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "license": "MIT" }, "node_modules/node-readfiles": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", - "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", "license": "MIT", "dependencies": { "es6-promise": "^3.2.1" @@ -21538,14 +19653,10 @@ }, "node_modules/node-readfiles/node_modules/es6-promise": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.54", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", - "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "version": "2.0.53", "license": "MIT", "engines": { "node": ">=18" @@ -21553,8 +19664,6 @@ }, "node_modules/normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -21574,8 +19683,6 @@ }, "node_modules/npm-run-path": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "license": "MIT", "dependencies": { "path-key": "^3.0.0" @@ -21586,14 +19693,10 @@ }, "node_modules/nprogress": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", - "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", "license": "MIT" }, "node_modules/nth-check": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" @@ -21604,8 +19707,6 @@ }, "node_modules/null-loader": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", - "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", "license": "MIT", "dependencies": { "loader-utils": "^2.0.0", @@ -21624,9 +19725,8 @@ }, "node_modules/null-loader/node_modules/ajv": { "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -21640,8 +19740,6 @@ }, "node_modules/null-loader/node_modules/ajv-keywords": { "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" @@ -21649,14 +19747,10 @@ }, "node_modules/null-loader/node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "license": "MIT" }, "node_modules/null-loader/node_modules/schema-utils": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.8", @@ -21673,8 +19767,6 @@ }, "node_modules/oas-kit-common": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", - "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", "license": "BSD-3-Clause", "dependencies": { "fast-safe-stringify": "^2.0.7" @@ -21682,8 +19774,6 @@ }, "node_modules/oas-linter": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", - "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", "license": "BSD-3-Clause", "dependencies": { "@exodus/schemasafe": "^1.0.0-rc.2", @@ -21696,8 +19786,6 @@ }, "node_modules/oas-linter/node_modules/yaml": { "version": "1.10.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", - "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", "engines": { "node": ">= 6" @@ -21705,8 +19793,6 @@ }, "node_modules/oas-resolver": { "version": "2.5.6", - "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", - "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", "license": "BSD-3-Clause", "dependencies": { "node-fetch-h2": "^2.3.0", @@ -21724,8 +19810,6 @@ }, "node_modules/oas-resolver-browser": { "version": "2.5.6", - "resolved": "https://registry.npmjs.org/oas-resolver-browser/-/oas-resolver-browser-2.5.6.tgz", - "integrity": "sha512-Jw5elT/kwUJrnGaVuRWe1D7hmnYWB8rfDDjBnpQ+RYY/dzAewGXeTexXzt4fGEo6PUE4eqKqPWF79MZxxvMppA==", "license": "BSD-3-Clause", "dependencies": { "node-fetch-h2": "^2.3.0", @@ -21742,10 +19826,28 @@ "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, + "node_modules/oas-resolver-browser/node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/oas-resolver-browser/node_modules/ansi-styles": { + "version": "4.3.0", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/oas-resolver-browser/node_modules/cliui": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -21758,14 +19860,10 @@ }, "node_modules/oas-resolver-browser/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "node_modules/oas-resolver-browser/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -21776,10 +19874,18 @@ "node": ">=8" } }, + "node_modules/oas-resolver-browser/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/oas-resolver-browser/node_modules/wrap-ansi": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -21795,8 +19901,6 @@ }, "node_modules/oas-resolver-browser/node_modules/yaml": { "version": "1.10.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", - "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", "engines": { "node": ">= 6" @@ -21804,8 +19908,6 @@ }, "node_modules/oas-resolver-browser/node_modules/yargs": { "version": "17.7.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -21820,10 +19922,28 @@ "node": ">=12" } }, + "node_modules/oas-resolver/node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/oas-resolver/node_modules/ansi-styles": { + "version": "4.3.0", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/oas-resolver/node_modules/cliui": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -21836,14 +19956,10 @@ }, "node_modules/oas-resolver/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "node_modules/oas-resolver/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -21854,10 +19970,18 @@ "node": ">=8" } }, + "node_modules/oas-resolver/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/oas-resolver/node_modules/wrap-ansi": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -21873,8 +19997,6 @@ }, "node_modules/oas-resolver/node_modules/yaml": { "version": "1.10.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", - "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", "engines": { "node": ">= 6" @@ -21882,8 +20004,6 @@ }, "node_modules/oas-resolver/node_modules/yargs": { "version": "17.7.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -21900,8 +20020,6 @@ }, "node_modules/oas-schema-walker": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", - "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", "license": "BSD-3-Clause", "funding": { "url": "https://github.com/Mermade/oas-kit?sponsor=1" @@ -21909,8 +20027,6 @@ }, "node_modules/oas-validator": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", - "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", "license": "BSD-3-Clause", "dependencies": { "call-me-maybe": "^1.0.1", @@ -21928,17 +20044,15 @@ }, "node_modules/oas-validator/node_modules/yaml": { "version": "1.10.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", - "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", "engines": { "node": ">= 6" } }, "node_modules/oauth4webapi": { - "version": "3.8.7", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.7.tgz", - "integrity": "sha512-4RxcKxXjuItDFZ20RRPf4YTw3kpeXJyCgJFxVzJ068A7PNJ18st2Dg90tlC1LkSDS0GecroagCLHYEIVUhCAkw==", + "version": "3.8.8", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.8.tgz", + "integrity": "sha512-8N28E+a/oxfXWBgOMt+ZP/JUf/XR+IFbvkAEPP3gznXOMv9BpAAwiIj0TFNz3tGTPc0ZQ8zmWBNgN1nAys0gng==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -21946,8 +20060,6 @@ }, "node_modules/object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -21955,8 +20067,6 @@ }, "node_modules/object-hash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "license": "MIT", "engines": { "node": ">= 6" @@ -21964,8 +20074,6 @@ }, "node_modules/object-inspect": { "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -21976,8 +20084,6 @@ }, "node_modules/object-keys": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -21985,8 +20091,6 @@ }, "node_modules/object.assign": { "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -22005,14 +20109,10 @@ }, "node_modules/obuf": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "license": "MIT" }, "node_modules/on-finished": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -22023,8 +20123,6 @@ }, "node_modules/on-headers": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -22032,8 +20130,6 @@ }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { "wrappy": "1" @@ -22041,8 +20137,6 @@ }, "node_modules/onetime": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -22056,8 +20150,6 @@ }, "node_modules/open": { "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "license": "MIT", "dependencies": { "define-lazy-prop": "^2.0.0", @@ -22073,8 +20165,6 @@ }, "node_modules/openapi-to-postmanv2": { "version": "6.3.3", - "resolved": "https://registry.npmjs.org/openapi-to-postmanv2/-/openapi-to-postmanv2-6.3.3.tgz", - "integrity": "sha512-o0u6qqMMRLt7eAyFBpL/lCresQ1VqQFGe6/iPMBvNkWDXrpgGo5jo+YnP1e0MwF3tdUKLfkHssufGWMdojpPLg==", "license": "Apache-2.0", "dependencies": { "ajv": "^8.11.0", @@ -22105,8 +20195,6 @@ }, "node_modules/openapi-to-postmanv2/node_modules/ajv-formats": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -22122,14 +20210,10 @@ }, "node_modules/openapi-to-postmanv2/node_modules/commander": { "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, "node_modules/openapi-to-postmanv2/node_modules/yaml": { "version": "1.10.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", - "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", "engines": { "node": ">= 6" @@ -22137,23 +20221,30 @@ }, "node_modules/openapi-types": { "version": "12.1.3", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", - "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", "license": "MIT" }, "node_modules/opener": { "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", "license": "(WTFPL OR MIT)", "bin": { "opener": "bin/opener-bin.js" } }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/outdent": { + "version": "0.5.0", + "dev": true, + "license": "MIT" + }, "node_modules/ox": { - "version": "0.14.44", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.44.tgz", - "integrity": "sha512-O54qXXHEk4ySamMSyBpI0AeBegS5frxU6+LA6Jb9Sf6kMOxcTNaxYmwZfpOu22DIQsdKfgQxHq8EsAGaoBQScA==", + "version": "0.14.34", "dev": true, "funding": [ { @@ -22183,8 +20274,6 @@ }, "node_modules/ox/node_modules/@noble/curves": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", - "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", "dev": true, "license": "MIT", "dependencies": { @@ -22199,8 +20288,6 @@ }, "node_modules/ox/node_modules/@noble/hashes": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, "license": "MIT", "engines": { @@ -22212,8 +20299,6 @@ }, "node_modules/ox/node_modules/eventemitter3": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "dev": true, "license": "MIT" }, @@ -22271,8 +20356,6 @@ }, "node_modules/oxfmt/node_modules/tinypool": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.0.tgz", - "integrity": "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==", "dev": true, "license": "MIT", "engines": { @@ -22280,9 +20363,9 @@ } }, "node_modules/oxlint": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.81.0.tgz", - "integrity": "sha512-HyrJYqeoOCL0iqaLEzGewGT48ZX99P3hxYh8udAF9RGGIghSamkXE4ClUyBpEDNqasamThgmlPbuMOe7SAZmHg==", + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.82.0.tgz", + "integrity": "sha512-+iFM1BGw1ntYJt3QngbJmjbrGxPaKMUADOXOijpWGnYcBPq8YZnQftSS1C+pVcDYy9YxqDVJKQqQkTazTQMboQ==", "dev": true, "license": "MIT", "bin": { @@ -22295,25 +20378,25 @@ "url": "https://github.com/sponsors/oxc-project" }, "optionalDependencies": { - "@oxlint/binding-android-arm-eabi": "1.81.0", - "@oxlint/binding-android-arm64": "1.81.0", - "@oxlint/binding-darwin-arm64": "1.81.0", - "@oxlint/binding-darwin-x64": "1.81.0", - "@oxlint/binding-freebsd-x64": "1.81.0", - "@oxlint/binding-linux-arm-gnueabihf": "1.81.0", - "@oxlint/binding-linux-arm-musleabihf": "1.81.0", - "@oxlint/binding-linux-arm64-gnu": "1.81.0", - "@oxlint/binding-linux-arm64-musl": "1.81.0", - "@oxlint/binding-linux-ppc64-gnu": "1.81.0", - "@oxlint/binding-linux-riscv64-gnu": "1.81.0", - "@oxlint/binding-linux-riscv64-musl": "1.81.0", - "@oxlint/binding-linux-s390x-gnu": "1.81.0", - "@oxlint/binding-linux-x64-gnu": "1.81.0", - "@oxlint/binding-linux-x64-musl": "1.81.0", - "@oxlint/binding-openharmony-arm64": "1.81.0", - "@oxlint/binding-win32-arm64-msvc": "1.81.0", - "@oxlint/binding-win32-ia32-msvc": "1.81.0", - "@oxlint/binding-win32-x64-msvc": "1.81.0" + "@oxlint/binding-android-arm-eabi": "1.82.0", + "@oxlint/binding-android-arm64": "1.82.0", + "@oxlint/binding-darwin-arm64": "1.82.0", + "@oxlint/binding-darwin-x64": "1.82.0", + "@oxlint/binding-freebsd-x64": "1.82.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.82.0", + "@oxlint/binding-linux-arm-musleabihf": "1.82.0", + "@oxlint/binding-linux-arm64-gnu": "1.82.0", + "@oxlint/binding-linux-arm64-musl": "1.82.0", + "@oxlint/binding-linux-ppc64-gnu": "1.82.0", + "@oxlint/binding-linux-riscv64-gnu": "1.82.0", + "@oxlint/binding-linux-riscv64-musl": "1.82.0", + "@oxlint/binding-linux-s390x-gnu": "1.82.0", + "@oxlint/binding-linux-x64-gnu": "1.82.0", + "@oxlint/binding-linux-x64-musl": "1.82.0", + "@oxlint/binding-openharmony-arm64": "1.82.0", + "@oxlint/binding-win32-arm64-msvc": "1.82.0", + "@oxlint/binding-win32-ia32-msvc": "1.82.0", + "@oxlint/binding-win32-x64-msvc": "1.82.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", @@ -22330,10 +20413,9 @@ }, "node_modules/oxlint-tsgolint": { "version": "7.0.2001", - "resolved": "https://registry.npmjs.org/oxlint-tsgolint/-/oxlint-tsgolint-7.0.2001.tgz", - "integrity": "sha512-KjK/XLcXr1DSyonKhsuFqJRiuKqcyG9j3LJ8nkOsrLzGvodBPqzHOKauy10asLMDI0sUpvb+1sxlzff3udZvfg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "tsgolint": "bin/tsgolint.js" }, @@ -22346,10 +20428,27 @@ "@oxlint-tsgolint/win32-x64": "7.0.2001" } }, + "node_modules/p-filter": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-map": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-filter/node_modules/p-map": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/p-finally": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", "license": "MIT", "engines": { "node": ">=4" @@ -22357,8 +20456,6 @@ }, "node_modules/p-limit": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -22373,8 +20470,6 @@ }, "node_modules/p-locate": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -22389,8 +20484,6 @@ }, "node_modules/p-map": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" @@ -22404,8 +20497,6 @@ }, "node_modules/p-queue": { "version": "6.6.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", - "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", "license": "MIT", "dependencies": { "eventemitter3": "^4.0.4", @@ -22420,14 +20511,10 @@ }, "node_modules/p-queue/node_modules/eventemitter3": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, "node_modules/p-retry": { "version": "6.2.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", - "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", "license": "MIT", "dependencies": { "@types/retry": "0.12.2", @@ -22443,8 +20530,6 @@ }, "node_modules/p-timeout": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", "license": "MIT", "dependencies": { "p-finally": "^1.0.0" @@ -22453,10 +20538,16 @@ "node": ">=8" } }, + "node_modules/p-try": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json": { "version": "10.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-10.0.1.tgz", - "integrity": "sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==", "license": "MIT", "dependencies": { "ky": "^1.2.0", @@ -22473,14 +20564,10 @@ }, "node_modules/package-manager-detector": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", - "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", "license": "MIT" }, "node_modules/pako": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.2.0.tgz", - "integrity": "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==", "funding": [ { "type": "github", @@ -22495,8 +20582,6 @@ }, "node_modules/param-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", "license": "MIT", "dependencies": { "dot-case": "^3.0.4", @@ -22505,8 +20590,6 @@ }, "node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -22517,8 +20600,6 @@ }, "node_modules/parse-entities": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", - "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", "license": "MIT", "dependencies": { "@types/unist": "^2.0.0", @@ -22536,14 +20617,10 @@ }, "node_modules/parse-entities/node_modules/@types/unist": { "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, "node_modules/parse-json": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -22560,8 +20637,6 @@ }, "node_modules/parse-ms": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", - "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", "dev": true, "license": "MIT", "engines": { @@ -22573,14 +20648,10 @@ }, "node_modules/parse-numeric-range": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", - "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==", "license": "ISC" }, "node_modules/parse5": { "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "license": "MIT", "dependencies": { "entities": "^6.0.0" @@ -22591,8 +20662,6 @@ }, "node_modules/parse5-htmlparser2-tree-adapter": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", "license": "MIT", "dependencies": { "domhandler": "^5.0.3", @@ -22604,8 +20673,6 @@ }, "node_modules/parse5-parser-stream": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", "license": "MIT", "dependencies": { "parse5": "^7.0.0" @@ -22616,8 +20683,6 @@ }, "node_modules/parse5/node_modules/entities": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -22628,8 +20693,6 @@ }, "node_modules/parseurl": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -22637,30 +20700,172 @@ }, "node_modules/pascal-case": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, + "node_modules/patch-package": { + "version": "6.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "cross-spawn": "^6.0.5", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "is-ci": "^2.0.0", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^5.6.0", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^1.10.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=10", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/ci-info": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/patch-package/node_modules/cross-spawn": { + "version": "6.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/patch-package/node_modules/fs-extra": { + "version": "9.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/patch-package/node_modules/is-ci": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/patch-package/node_modules/open": { + "version": "7.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/patch-package/node_modules/path-key": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/patch-package/node_modules/semver": { + "version": "5.7.2", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/patch-package/node_modules/shebang-command": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/patch-package/node_modules/shebang-regex": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/patch-package/node_modules/which": { + "version": "1.3.1", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/patch-package/node_modules/yaml": { + "version": "1.10.3", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/path-browserify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", "license": "MIT" }, "node_modules/path-data-parser": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", - "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", "license": "MIT" }, "node_modules/path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { @@ -22669,8 +20874,6 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -22678,14 +20881,10 @@ }, "node_modules/path-is-inside": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", "license": "(WTFPL OR MIT)" }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", "engines": { "node": ">=8" @@ -22693,14 +20892,10 @@ }, "node_modules/path-parse": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, "node_modules/path-scurry": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -22714,20 +20909,8 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/path-to-regexp": { "version": "1.9.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", - "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "license": "MIT", "dependencies": { "isarray": "0.0.1" @@ -22735,23 +20918,25 @@ }, "node_modules/path-type": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/pathval": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -22760,10 +20945,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pirates": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "license": "MIT", "engines": { "node": ">= 6" @@ -22771,8 +20962,6 @@ }, "node_modules/pkg-dir": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", "license": "MIT", "dependencies": { "find-up": "^6.3.0" @@ -22786,8 +20975,6 @@ }, "node_modules/pkg-dir/node_modules/find-up": { "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", "license": "MIT", "dependencies": { "locate-path": "^7.1.0", @@ -22802,8 +20989,6 @@ }, "node_modules/pkg-dir/node_modules/locate-path": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "license": "MIT", "dependencies": { "p-locate": "^6.0.0" @@ -22817,8 +21002,6 @@ }, "node_modules/pkg-dir/node_modules/p-limit": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "license": "MIT", "dependencies": { "yocto-queue": "^1.0.0" @@ -22832,8 +21015,6 @@ }, "node_modules/pkg-dir/node_modules/p-locate": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", "license": "MIT", "dependencies": { "p-limit": "^4.0.0" @@ -22847,8 +21028,6 @@ }, "node_modules/pkg-dir/node_modules/path-exists": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -22856,8 +21035,6 @@ }, "node_modules/pkg-dir/node_modules/yocto-queue": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", - "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", "license": "MIT", "engines": { "node": ">=12.20" @@ -22868,8 +21045,6 @@ }, "node_modules/pkijs": { "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", - "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", "license": "BSD-3-Clause", "dependencies": { "@noble/hashes": "1.4.0", @@ -22885,8 +21060,6 @@ }, "node_modules/pkijs/node_modules/@noble/hashes": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "license": "MIT", "engines": { "node": ">= 16" @@ -22897,8 +21070,6 @@ }, "node_modules/pluralize": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", "license": "MIT", "engines": { "node": ">=4" @@ -22906,14 +21077,10 @@ }, "node_modules/points-on-curve": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", - "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", "license": "MIT" }, "node_modules/points-on-path": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", - "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", "license": "MIT", "dependencies": { "path-data-parser": "0.1.0", @@ -22928,8 +21095,6 @@ }, "node_modules/postcss": { "version": "8.5.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", - "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "funding": [ { "type": "opencollective", @@ -22945,6 +21110,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", @@ -22956,8 +21122,6 @@ }, "node_modules/postcss-attribute-case-insensitive": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", - "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", "funding": [ { "type": "github", @@ -22980,9 +21144,7 @@ } }, "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "version": "7.1.5", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -22994,8 +21156,6 @@ }, "node_modules/postcss-calc": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.11", @@ -23010,8 +21170,6 @@ }, "node_modules/postcss-clamp": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", - "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -23025,8 +21183,6 @@ }, "node_modules/postcss-color-functional-notation": { "version": "7.0.12", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.12.tgz", - "integrity": "sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw==", "funding": [ { "type": "github", @@ -23054,8 +21210,6 @@ }, "node_modules/postcss-color-hex-alpha": { "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", - "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", "funding": [ { "type": "github", @@ -23080,8 +21234,6 @@ }, "node_modules/postcss-color-rebeccapurple": { "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", - "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", "funding": [ { "type": "github", @@ -23106,8 +21258,6 @@ }, "node_modules/postcss-colormin": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", - "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -23124,8 +21274,6 @@ }, "node_modules/postcss-convert-values": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", - "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -23140,8 +21288,6 @@ }, "node_modules/postcss-custom-media": { "version": "11.0.6", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz", - "integrity": "sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw==", "funding": [ { "type": "github", @@ -23168,8 +21314,6 @@ }, "node_modules/postcss-custom-properties": { "version": "14.0.6", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz", - "integrity": "sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ==", "funding": [ { "type": "github", @@ -23197,8 +21341,6 @@ }, "node_modules/postcss-custom-selectors": { "version": "8.0.5", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz", - "integrity": "sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg==", "funding": [ { "type": "github", @@ -23224,9 +21366,7 @@ } }, "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "version": "7.1.5", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -23238,8 +21378,6 @@ }, "node_modules/postcss-dir-pseudo-class": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", - "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", "funding": [ { "type": "github", @@ -23262,9 +21400,7 @@ } }, "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "version": "7.1.5", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -23276,8 +21412,6 @@ }, "node_modules/postcss-discard-comments": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", - "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" @@ -23288,8 +21422,6 @@ }, "node_modules/postcss-discard-duplicates": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", - "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" @@ -23300,8 +21432,6 @@ }, "node_modules/postcss-discard-empty": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" @@ -23312,8 +21442,6 @@ }, "node_modules/postcss-discard-overridden": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", - "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" @@ -23324,8 +21452,6 @@ }, "node_modules/postcss-discard-unused": { "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", - "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.16" @@ -23339,8 +21465,6 @@ }, "node_modules/postcss-double-position-gradients": { "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz", - "integrity": "sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g==", "funding": [ { "type": "github", @@ -23366,8 +21490,6 @@ }, "node_modules/postcss-focus-visible": { "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", - "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", "funding": [ { "type": "github", @@ -23390,9 +21512,7 @@ } }, "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "version": "7.1.5", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -23404,8 +21524,6 @@ }, "node_modules/postcss-focus-within": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", - "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", "funding": [ { "type": "github", @@ -23428,9 +21546,7 @@ } }, "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "version": "7.1.5", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -23442,8 +21558,6 @@ }, "node_modules/postcss-font-variant": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", "license": "MIT", "peerDependencies": { "postcss": "^8.1.0" @@ -23451,8 +21565,6 @@ }, "node_modules/postcss-gap-properties": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", - "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", "funding": [ { "type": "github", @@ -23473,8 +21585,6 @@ }, "node_modules/postcss-image-set-function": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", - "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", "funding": [ { "type": "github", @@ -23499,8 +21609,6 @@ }, "node_modules/postcss-import": { "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", @@ -23516,8 +21624,6 @@ }, "node_modules/postcss-js": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", - "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", "funding": [ { "type": "opencollective", @@ -23541,8 +21647,6 @@ }, "node_modules/postcss-lab-function": { "version": "7.0.12", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.12.tgz", - "integrity": "sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w==", "funding": [ { "type": "github", @@ -23570,8 +21674,6 @@ }, "node_modules/postcss-load-config": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", "funding": [ { "type": "opencollective", @@ -23605,8 +21707,6 @@ }, "node_modules/postcss-loader": { "version": "7.3.4", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", - "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", "license": "MIT", "dependencies": { "cosmiconfig": "^8.3.5", @@ -23627,8 +21727,6 @@ }, "node_modules/postcss-logical": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.1.0.tgz", - "integrity": "sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA==", "funding": [ { "type": "github", @@ -23652,8 +21750,6 @@ }, "node_modules/postcss-merge-idents": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", - "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", "license": "MIT", "dependencies": { "cssnano-utils": "^4.0.2", @@ -23668,8 +21764,6 @@ }, "node_modules/postcss-merge-longhand": { "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", - "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", @@ -23684,8 +21778,6 @@ }, "node_modules/postcss-merge-rules": { "version": "6.1.1", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", - "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -23702,8 +21794,6 @@ }, "node_modules/postcss-minify-font-values": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", - "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -23717,8 +21807,6 @@ }, "node_modules/postcss-minify-gradients": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", - "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", "license": "MIT", "dependencies": { "colord": "^2.9.3", @@ -23734,8 +21822,6 @@ }, "node_modules/postcss-minify-params": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", - "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -23751,8 +21837,6 @@ }, "node_modules/postcss-minify-selectors": { "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", - "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.16" @@ -23766,8 +21850,6 @@ }, "node_modules/postcss-modules-extract-imports": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" @@ -23778,8 +21860,6 @@ }, "node_modules/postcss-modules-local-by-default": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", - "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "license": "MIT", "dependencies": { "icss-utils": "^5.0.0", @@ -23794,9 +21874,7 @@ } }, "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "version": "7.1.5", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -23808,8 +21886,6 @@ }, "node_modules/postcss-modules-scope": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", - "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "license": "ISC", "dependencies": { "postcss-selector-parser": "^7.0.0" @@ -23822,9 +21898,7 @@ } }, "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "version": "7.1.5", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -23836,8 +21910,6 @@ }, "node_modules/postcss-modules-values": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", "license": "ISC", "dependencies": { "icss-utils": "^5.0.0" @@ -23851,8 +21923,6 @@ }, "node_modules/postcss-nested": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", "funding": [ { "type": "opencollective", @@ -23876,8 +21946,6 @@ }, "node_modules/postcss-nesting": { "version": "13.0.2", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", - "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", "funding": [ { "type": "github", @@ -23903,8 +21971,6 @@ }, "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", - "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==", "funding": [ { "type": "github", @@ -23925,8 +21991,6 @@ }, "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", "funding": [ { "type": "github", @@ -23946,10 +22010,9 @@ } }, "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "version": "7.1.5", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -23960,8 +22023,6 @@ }, "node_modules/postcss-normalize-charset": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", - "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" @@ -23972,8 +22033,6 @@ }, "node_modules/postcss-normalize-display-values": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", - "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -23987,8 +22046,6 @@ }, "node_modules/postcss-normalize-positions": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", - "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -24002,8 +22059,6 @@ }, "node_modules/postcss-normalize-repeat-style": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", - "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -24017,8 +22072,6 @@ }, "node_modules/postcss-normalize-string": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", - "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -24032,8 +22085,6 @@ }, "node_modules/postcss-normalize-timing-functions": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", - "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -24047,8 +22098,6 @@ }, "node_modules/postcss-normalize-unicode": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", - "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -24063,8 +22112,6 @@ }, "node_modules/postcss-normalize-url": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", - "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -24078,8 +22125,6 @@ }, "node_modules/postcss-normalize-whitespace": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", - "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -24093,8 +22138,6 @@ }, "node_modules/postcss-opacity-percentage": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", - "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", "funding": [ { "type": "kofi", @@ -24115,8 +22158,6 @@ }, "node_modules/postcss-ordered-values": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", - "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", "license": "MIT", "dependencies": { "cssnano-utils": "^4.0.2", @@ -24131,8 +22172,6 @@ }, "node_modules/postcss-overflow-shorthand": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", - "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", "funding": [ { "type": "github", @@ -24156,8 +22195,6 @@ }, "node_modules/postcss-page-break": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", "license": "MIT", "peerDependencies": { "postcss": "^8" @@ -24165,8 +22202,6 @@ }, "node_modules/postcss-place": { "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", - "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", "funding": [ { "type": "github", @@ -24190,8 +22225,6 @@ }, "node_modules/postcss-preset-env": { "version": "10.6.1", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.6.1.tgz", - "integrity": "sha512-yrk74d9EvY+W7+lO9Aj1QmjWY9q5NsKjK2V9drkOPZB/X6KZ0B3igKsHUYakb7oYVhnioWypQX3xGuePf89f3g==", "funding": [ { "type": "github", @@ -24285,8 +22318,6 @@ }, "node_modules/postcss-pseudo-class-any-link": { "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", - "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", "funding": [ { "type": "github", @@ -24309,9 +22340,7 @@ } }, "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "version": "7.1.5", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -24323,8 +22352,6 @@ }, "node_modules/postcss-reduce-idents": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", - "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -24338,8 +22365,6 @@ }, "node_modules/postcss-reduce-initial": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", - "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -24354,8 +22379,6 @@ }, "node_modules/postcss-reduce-transforms": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", - "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -24369,8 +22392,6 @@ }, "node_modules/postcss-replace-overflow-wrap": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", "license": "MIT", "peerDependencies": { "postcss": "^8.0.3" @@ -24378,8 +22399,6 @@ }, "node_modules/postcss-selector-not": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", - "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", "funding": [ { "type": "github", @@ -24402,9 +22421,7 @@ } }, "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "version": "7.1.5", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -24416,8 +22433,6 @@ }, "node_modules/postcss-selector-parser": { "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -24429,8 +22444,6 @@ }, "node_modules/postcss-sort-media-queries": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", - "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", "license": "MIT", "dependencies": { "sort-css-media-queries": "2.2.0" @@ -24444,8 +22457,6 @@ }, "node_modules/postcss-svgo": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", - "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", @@ -24460,8 +22471,6 @@ }, "node_modules/postcss-unique-selectors": { "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", - "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.16" @@ -24475,14 +22484,10 @@ }, "node_modules/postcss-value-parser": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, "node_modules/postcss-zindex": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", - "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" @@ -24493,8 +22498,6 @@ }, "node_modules/postman-code-generators": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/postman-code-generators/-/postman-code-generators-2.1.1.tgz", - "integrity": "sha512-+egQK1Jf9a92QP23vRTKcDLOthIQmI7WI4czEsZq/wgguLMnVHJ26KlT8AVtpAdVw28hqUbHwicerYxRWCfjoA==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -24510,8 +22513,6 @@ }, "node_modules/postman-collection": { "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-5.3.1.tgz", - "integrity": "sha512-+ixY4KEGerw3I5dE6obXgXx31na8URU5ODNIA6Rjkbt3/BUpNRk03pxUAZFHr5dDXwCikJSa7pt5o0x1QXT77w==", "license": "Apache-2.0", "dependencies": { "@faker-js/faker": "5.5.3", @@ -24532,8 +22533,6 @@ }, "node_modules/postman-collection/node_modules/iconv-lite": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -24544,8 +22543,6 @@ }, "node_modules/postman-collection/node_modules/semver": { "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -24556,8 +22553,6 @@ }, "node_modules/postman-url-encoder": { "version": "3.0.8", - "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-3.0.8.tgz", - "integrity": "sha512-EOgUMBazo7JNP4TDrd64TsooCiWzzo4143Ws8E8WYGEpn2PKpq+S4XRTDhuRTYHm3VKOpUZs7ZYZq7zSDuesqA==", "license": "Apache-2.0", "dependencies": { "punycode": "^2.3.1" @@ -24568,9 +22563,6 @@ }, "node_modules/prebuild-install": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", "license": "MIT", "dependencies": { "detect-libc": "^2.0.0", @@ -24593,10 +22585,22 @@ "node": ">=10" } }, + "node_modules/prettier": { + "version": "2.8.8", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-error": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", "license": "MIT", "dependencies": { "lodash": "^4.17.20", @@ -24604,9 +22608,7 @@ } }, "node_modules/pretty-ms": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.1.tgz", - "integrity": "sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==", + "version": "9.3.0", "dev": true, "license": "MIT", "dependencies": { @@ -24621,8 +22623,6 @@ }, "node_modules/pretty-time": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", - "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", "license": "MIT", "engines": { "node": ">=4" @@ -24630,8 +22630,6 @@ }, "node_modules/prism-react-renderer": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", - "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", "license": "MIT", "dependencies": { "@types/prismjs": "^1.26.0", @@ -24643,8 +22641,6 @@ }, "node_modules/prismjs": { "version": "1.30.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", - "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", "license": "MIT", "engines": { "node": ">=6" @@ -24652,8 +22648,6 @@ }, "node_modules/process": { "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "license": "MIT", "engines": { "node": ">= 0.6.0" @@ -24661,14 +22655,10 @@ }, "node_modules/process-nextick-args": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, "node_modules/prompts": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "license": "MIT", "dependencies": { "kleur": "^3.0.3", @@ -24680,8 +22670,6 @@ }, "node_modules/prool": { "version": "0.2.14", - "resolved": "https://registry.npmjs.org/prool/-/prool-0.2.14.tgz", - "integrity": "sha512-GAu9hAJqIMac/16sHTm//ya8qfqwh0M+czyRcQYlL8daXYHhSfxOCbNv9SFkmRQ1wZhZHDvDtfqNJZmCcwzJaw==", "dev": true, "funding": [ { @@ -24716,8 +22704,6 @@ }, "node_modules/prool/node_modules/execa": { "version": "9.6.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", - "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", "dev": true, "license": "MIT", "dependencies": { @@ -24743,8 +22729,6 @@ }, "node_modules/prool/node_modules/get-stream": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, "license": "MIT", "dependencies": { @@ -24760,8 +22744,6 @@ }, "node_modules/prool/node_modules/human-signals": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", - "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -24770,8 +22752,6 @@ }, "node_modules/prool/node_modules/is-stream": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, "license": "MIT", "engines": { @@ -24783,8 +22763,6 @@ }, "node_modules/prool/node_modules/npm-run-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", - "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, "license": "MIT", "dependencies": { @@ -24800,8 +22778,6 @@ }, "node_modules/prool/node_modules/path-key": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", "engines": { @@ -24813,8 +22789,6 @@ }, "node_modules/prool/node_modules/strip-final-newline": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, "license": "MIT", "engines": { @@ -24826,8 +22800,6 @@ }, "node_modules/prop-types": { "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -24837,8 +22809,6 @@ }, "node_modules/property-information": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", - "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", "license": "MIT", "funding": { "type": "github", @@ -24847,14 +22817,10 @@ }, "node_modules/proto-list": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "license": "ISC" }, "node_modules/proxy-addr": { "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -24866,8 +22832,6 @@ }, "node_modules/proxy-addr/node_modules/ipaddr.js": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -24875,8 +22839,6 @@ }, "node_modules/proxy-from-env": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "license": "MIT", "engines": { "node": ">=10" @@ -24884,8 +22846,6 @@ }, "node_modules/pump": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -24894,8 +22854,6 @@ }, "node_modules/punycode": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "license": "MIT", "engines": { "node": ">=6" @@ -24903,8 +22861,6 @@ }, "node_modules/punycode.js": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "dev": true, "license": "MIT", "engines": { @@ -24913,8 +22869,6 @@ }, "node_modules/pupa": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.3.0.tgz", - "integrity": "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==", "license": "MIT", "dependencies": { "escape-goat": "^4.0.0" @@ -24928,8 +22882,6 @@ }, "node_modules/pvtsutils": { "version": "1.3.6", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", - "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", "license": "MIT", "dependencies": { "tslib": "^2.8.1" @@ -24937,17 +22889,13 @@ }, "node_modules/pvutils": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz", - "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", "license": "MIT", "engines": { "node": ">=16.0.0" } }, "node_modules/qs": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", - "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "version": "6.15.3", "license": "BSD-3-Clause", "dependencies": { "es-define-property": "^1.0.1", @@ -24960,10 +22908,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quansync": { + "version": "0.2.11", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "funding": [ { "type": "github", @@ -24982,8 +22943,6 @@ }, "node_modules/quick-lru": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "license": "MIT", "engines": { "node": ">=10" @@ -24994,8 +22953,6 @@ }, "node_modules/range-parser": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -25003,8 +22960,6 @@ }, "node_modules/raw-body": { "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -25018,8 +22973,6 @@ }, "node_modules/raw-body/node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -25027,8 +22980,6 @@ }, "node_modules/raw-body/node_modules/iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -25039,8 +22990,6 @@ }, "node_modules/rc": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { "deep-extend": "^0.6.0", @@ -25053,43 +23002,31 @@ } }, "node_modules/react": { - "version": "19.1.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.4.tgz", - "integrity": "sha512-DHINL3PAmPUiK1uszfbKiXqfE03eszdt5BpVSuEAHb5nfmNPwnsy7g39h2t8aXFc/Bv99GH81s+j8dobtD+jOw==", + "version": "19.2.8", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.1.4", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.4.tgz", - "integrity": "sha512-s2868ab/xo2SI6H4106A7aFI8Mrqa4xC6HZT/pBzYyQ3cBLqa88hu47xYD8xf+uECleN698Awn7RCWlkTiKnqQ==", + "version": "19.2.8", "license": "MIT", + "peer": true, "dependencies": { - "scheduler": "^0.26.0" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.1.4" + "react": "^19.2.8" } }, - "node_modules/react-dom/node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT" - }, "node_modules/react-fast-compare": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", "license": "MIT" }, "node_modules/react-helmet-async": { "name": "@slorber/react-helmet-async", "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@slorber/react-helmet-async/-/react-helmet-async-1.3.0.tgz", - "integrity": "sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A==", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.12.5", @@ -25104,10 +23041,9 @@ } }, "node_modules/react-hook-form": { - "version": "7.87.0", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.87.0.tgz", - "integrity": "sha512-zhFzWvLxNHH+8839OnZcUxgMZw88ah2jZWDWvKWgF3Tpbnd0vKL+dlcuU3nZVWESZQjd81EW8K+wU+cYfYAc0w==", + "version": "7.86.0", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -25121,14 +23057,10 @@ }, "node_modules/react-is": { "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, "node_modules/react-json-view-lite": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-2.5.0.tgz", - "integrity": "sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g==", "license": "MIT", "engines": { "node": ">=18" @@ -25139,14 +23071,10 @@ }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", "license": "MIT" }, "node_modules/react-live": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/react-live/-/react-live-4.1.8.tgz", - "integrity": "sha512-B2SgNqwPuS2ekqj4lcxi5TibEcjWkdVyYykBEUBshPAPDQ527x2zPEZg560n8egNtAjUpwXFQm7pcXV65aAYmg==", "license": "MIT", "dependencies": { "prism-react-renderer": "^2.4.0", @@ -25165,9 +23093,8 @@ "node_modules/react-loadable": { "name": "@docusaurus/react-loadable", "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", - "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/react": "*" }, @@ -25177,8 +23104,6 @@ }, "node_modules/react-loadable-ssr-addon-v5-slorber": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.3.tgz", - "integrity": "sha512-GXfh9VLwB5ERaCsU6RULh7tkemeX15aNh6wuMEBtfdyMa7fFG8TXrhXlx1SoEK2Ty/l6XIkzzYIQmyaWW3JgdQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.3" @@ -25193,14 +23118,10 @@ }, "node_modules/react-magic-dropzone": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/react-magic-dropzone/-/react-magic-dropzone-1.0.1.tgz", - "integrity": "sha512-0BIROPARmXHpk4AS3eWBOsewxoM5ndk2psYP/JmbCq8tz3uR2LIV1XiroZ9PKrmDRMctpW+TvsBCtWasuS8vFA==", "license": "MIT" }, "node_modules/react-markdown": { "version": "10.1.0", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", - "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -25226,8 +23147,6 @@ }, "node_modules/react-modal": { "version": "3.16.3", - "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.3.tgz", - "integrity": "sha512-yCYRJB5YkeQDQlTt17WGAgFJ7jr2QYcWa1SHqZ3PluDmnKJ/7+tVU+E6uKyZ0nODaeEj+xCpK4LcSnKXLMC0Nw==", "license": "MIT", "dependencies": { "exenv": "^1.2.0", @@ -25242,9 +23161,8 @@ }, "node_modules/react-redux": { "version": "9.3.0", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", - "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -25265,9 +23183,8 @@ }, "node_modules/react-router": { "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", - "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.13", "history": "^4.9.0", @@ -25285,8 +23202,6 @@ }, "node_modules/react-router-config": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", - "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.1.2" @@ -25298,8 +23213,6 @@ }, "node_modules/react-router-dom": { "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", - "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.13", @@ -25316,14 +23229,24 @@ }, "node_modules/read-cache": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.2.tgz", - "integrity": "sha512-/peqiBB/n07gQGLsWaHho3WfvUyRscw0gYTsEFMhrIe/nWLkYaf5SbKYjGYqtRV3aPwykJgF2VEMo1ac4bnsGA==", "license": "MIT" }, + "node_modules/read-yaml-file": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.5", + "js-yaml": "^3.6.1", + "pify": "^4.0.1", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/readable-stream": { "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -25336,8 +23259,6 @@ }, "node_modules/readdirp": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -25348,8 +23269,6 @@ }, "node_modules/rechoir": { "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", "dependencies": { "resolve": "^1.1.6" }, @@ -25359,8 +23278,6 @@ }, "node_modules/recma-build-jsx": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", - "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -25374,8 +23291,6 @@ }, "node_modules/recma-jsx": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", - "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", "license": "MIT", "dependencies": { "acorn-jsx": "^5.0.0", @@ -25394,8 +23309,6 @@ }, "node_modules/recma-parse": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", - "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -25410,8 +23323,6 @@ }, "node_modules/recma-stringify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", - "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -25426,14 +23337,11 @@ }, "node_modules/redux": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", - "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", "license": "MIT", "peerDependencies": { "redux": "^5.0.0" @@ -25441,14 +23349,10 @@ }, "node_modules/reflect-metadata": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "license": "Apache-2.0" }, "node_modules/reftools": { "version": "1.1.9", - "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", - "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", "license": "BSD-3-Clause", "funding": { "url": "https://github.com/Mermade/oas-kit?sponsor=1" @@ -25456,14 +23360,10 @@ }, "node_modules/regenerate": { "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", "license": "MIT" }, "node_modules/regenerate-unicode-properties": { "version": "10.2.2", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", - "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "license": "MIT", "dependencies": { "regenerate": "^1.4.2" @@ -25474,8 +23374,6 @@ }, "node_modules/regexpu-core": { "version": "6.4.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", - "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", "license": "MIT", "dependencies": { "regenerate": "^1.4.2", @@ -25491,8 +23389,6 @@ }, "node_modules/registry-auth-token": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", - "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", "license": "MIT", "dependencies": { "@pnpm/npm-conf": "^3.0.2" @@ -25503,8 +23399,6 @@ }, "node_modules/registry-url": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", - "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", "license": "MIT", "dependencies": { "rc": "1.2.8" @@ -25518,14 +23412,10 @@ }, "node_modules/regjsgen": { "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", "license": "MIT" }, "node_modules/regjsparser": { "version": "0.13.2", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", - "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.1.0" @@ -25536,8 +23426,6 @@ }, "node_modules/rehype-raw": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", - "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -25551,8 +23439,6 @@ }, "node_modules/rehype-recma": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", - "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -25566,8 +23452,6 @@ }, "node_modules/relateurl": { "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -25575,8 +23459,6 @@ }, "node_modules/remark-directive": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", - "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -25591,8 +23473,6 @@ }, "node_modules/remark-emoji": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-4.0.1.tgz", - "integrity": "sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.2", @@ -25607,8 +23487,6 @@ }, "node_modules/remark-frontmatter": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", - "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -25623,8 +23501,6 @@ }, "node_modules/remark-gfm": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", - "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -25641,8 +23517,6 @@ }, "node_modules/remark-mdx": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", - "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", "license": "MIT", "dependencies": { "mdast-util-mdx": "^3.0.0", @@ -25655,8 +23529,6 @@ }, "node_modules/remark-parse": { "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -25671,8 +23543,6 @@ }, "node_modules/remark-rehype": { "version": "11.1.2", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", - "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -25688,8 +23558,6 @@ }, "node_modules/remark-stringify": { "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -25703,8 +23571,6 @@ }, "node_modules/renderkid": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", "license": "MIT", "dependencies": { "css-select": "^4.1.3", @@ -25714,10 +23580,15 @@ "strip-ansi": "^6.0.1" } }, + "node_modules/renderkid/node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/renderkid/node_modules/css-select": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", @@ -25732,8 +23603,6 @@ }, "node_modules/renderkid/node_modules/dom-serializer": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", "license": "MIT", "dependencies": { "domelementtype": "^2.0.1", @@ -25746,8 +23615,6 @@ }, "node_modules/renderkid/node_modules/domhandler": { "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.2.0" @@ -25761,8 +23628,6 @@ }, "node_modules/renderkid/node_modules/domutils": { "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^1.0.1", @@ -25775,8 +23640,6 @@ }, "node_modules/renderkid/node_modules/entities": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "license": "BSD-2-Clause", "funding": { "url": "https://github.com/fb55/entities?sponsor=1" @@ -25784,8 +23647,6 @@ }, "node_modules/renderkid/node_modules/htmlparser2": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -25801,10 +23662,18 @@ "entities": "^2.0.0" } }, + "node_modules/renderkid/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -25812,8 +23681,6 @@ }, "node_modules/require-from-string": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -25821,28 +23688,20 @@ }, "node_modules/require-like": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==", "engines": { "node": "*" } }, "node_modules/requires-port": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "license": "MIT" }, "node_modules/reselect": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.3.0.tgz", - "integrity": "sha512-XGoLeRAVzUTcJ1qkxPQhDJyIZ5d6zzZD9nT7AEZOaaU9UbWclhycElmhO+VD5bFeLuzhPBaOV2oXC8uG35ZSpg==", "license": "MIT" }, "node_modules/resolve": { "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -25862,14 +23721,10 @@ }, "node_modules/resolve-alpn": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", "license": "MIT" }, "node_modules/resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "license": "MIT", "engines": { "node": ">=4" @@ -25877,8 +23732,6 @@ }, "node_modules/resolve-pathname": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", "license": "MIT" }, "node_modules/responselike": { @@ -25910,8 +23763,6 @@ }, "node_modules/retry": { "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "license": "MIT", "engines": { "node": ">= 4" @@ -25919,24 +23770,77 @@ }, "node_modules/reusify": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "2.7.1", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rimraf/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/robust-predicates": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", - "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", "license": "Unlicense" }, "node_modules/roughjs": { "version": "4.6.6", - "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", - "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", "license": "MIT", "dependencies": { "hachure-fill": "^0.5.2", @@ -25947,8 +23851,6 @@ }, "node_modules/rpc-websockets": { "version": "9.3.9", - "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.3.9.tgz", - "integrity": "sha512-2iQDaTB4g5fDB2ihrTFSJSibCEuxaRi1q7qTW7ZO9/M5/TC+ToHA4D9/ffNLEbAoHNNrcdeP05oATNk44SKZXA==", "license": "LGPL-3.0-only", "dependencies": { "@swc/helpers": "^0.5.11", @@ -25970,8 +23872,6 @@ }, "node_modules/rpc-websockets/node_modules/@types/ws": { "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -25979,8 +23879,6 @@ }, "node_modules/rtlcss": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", - "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", "license": "MIT", "dependencies": { "escalade": "^3.1.1", @@ -25997,8 +23895,6 @@ }, "node_modules/rtlcss/node_modules/strip-json-comments": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "license": "MIT", "engines": { "node": ">=8" @@ -26009,8 +23905,6 @@ }, "node_modules/run-applescript": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", "license": "MIT", "engines": { "node": ">=18" @@ -26021,8 +23915,6 @@ }, "node_modules/run-parallel": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "funding": [ { "type": "github", @@ -26044,14 +23936,10 @@ }, "node_modules/rw": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", "license": "BSD-3-Clause" }, "node_modules/rxjs": { "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" @@ -26059,8 +23947,6 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -26079,15 +23965,12 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, "node_modules/sass": { "version": "1.103.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.103.1.tgz", - "integrity": "sha512-9icZURbP51S6S0QGoyaeqk9uB06GNWxsFYWfH5RgpFgqK5FA8tJcM3AdVxrZEVJ7dz+L87nG95gBKf4VuaMHGw==", "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^5.0.0", "immutable": "^5.1.5", @@ -26105,10 +23988,7 @@ }, "node_modules/sass-loader": { "version": "16.0.8", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.8.tgz", - "integrity": "sha512-hcov4ZwZJIGbEuyNr9EmiTmZueyrxSToE6GOzoZnq5JM7ecRO7ttyvilPn+VmRsqiP16+VYZzVnGZj/hzZgKBA==", "license": "MIT", - "peer": true, "dependencies": { "neo-async": "^2.6.2" }, @@ -26146,8 +24026,6 @@ }, "node_modules/sass/node_modules/chokidar": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "license": "MIT", "dependencies": { "readdirp": "^5.0.0" @@ -26161,8 +24039,6 @@ }, "node_modules/sass/node_modules/readdirp": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", - "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -26174,8 +24050,6 @@ }, "node_modules/sax": { "version": "1.6.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", - "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" @@ -26183,20 +24057,14 @@ }, "node_modules/scheduler": { "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, "node_modules/schema-dts": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.5.tgz", - "integrity": "sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg==", "license": "Apache-2.0" }, "node_modules/schema-utils": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", @@ -26214,8 +24082,6 @@ }, "node_modules/schema-utils/node_modules/ajv-formats": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -26229,17 +24095,18 @@ } } }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, "node_modules/search-insights": { "version": "2.17.3", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", - "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", "license": "MIT", "peer": true }, "node_modules/section-matter": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", - "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", "license": "MIT", "dependencies": { "extend-shallow": "^2.0.1", @@ -26251,14 +24118,10 @@ }, "node_modules/select-hose": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", "license": "MIT" }, "node_modules/selfsigned": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz", - "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", "license": "MIT", "dependencies": { "@peculiar/x509": "^1.14.2", @@ -26270,8 +24133,6 @@ }, "node_modules/semver": { "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -26282,8 +24143,6 @@ }, "node_modules/semver-diff": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", - "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -26297,8 +24156,6 @@ }, "node_modules/send": { "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -26321,8 +24178,6 @@ }, "node_modules/send/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -26330,14 +24185,10 @@ }, "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/send/node_modules/mime": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "license": "MIT", "bin": { "mime": "cli.js" @@ -26348,17 +24199,13 @@ }, "node_modules/send/node_modules/range-parser": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/serialize-javascript": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.1.tgz", - "integrity": "sha512-k3CMsaIvvdSwm8oLB4MXSl0wH2/cwlH7xGcnRd2DaeRmBkbzYmyT8j0tsX60DwD1eRwHTpNpH8ljKu9oUT1MeQ==", + "version": "7.1.0", "license": "BSD-3-Clause", "engines": { "node": ">=20.0.0" @@ -26366,8 +24213,6 @@ }, "node_modules/serve-handler": { "version": "6.1.7", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz", - "integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==", "license": "MIT", "dependencies": { "bytes": "3.0.0", @@ -26379,10 +24224,20 @@ "range-parser": "1.2.0" } }, + "node_modules/serve-handler/node_modules/balanced-match": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/serve-handler/node_modules/brace-expansion": { + "version": "1.1.18", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/serve-handler/node_modules/mime-db": { "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -26390,8 +24245,6 @@ }, "node_modules/serve-handler/node_modules/mime-types": { "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", "license": "MIT", "dependencies": { "mime-db": "~1.33.0" @@ -26400,16 +24253,22 @@ "node": ">= 0.6" } }, + "node_modules/serve-handler/node_modules/minimatch": { + "version": "3.1.5", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/serve-handler/node_modules/path-to-regexp": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", - "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", "license": "MIT" }, "node_modules/serve-index": { "version": "1.9.2", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", - "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -26430,8 +24289,6 @@ }, "node_modules/serve-index/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -26439,8 +24296,6 @@ }, "node_modules/serve-index/node_modules/depd": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -26448,8 +24303,6 @@ }, "node_modules/serve-index/node_modules/http-errors": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "license": "MIT", "dependencies": { "depd": "~1.1.2", @@ -26464,14 +24317,10 @@ }, "node_modules/serve-index/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/serve-index/node_modules/statuses": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -26479,8 +24328,6 @@ }, "node_modules/serve-static": { "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", @@ -26494,8 +24341,6 @@ }, "node_modules/set-function-length": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -26511,14 +24356,10 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, "node_modules/shallow-clone": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "license": "MIT", "dependencies": { "kind-of": "^6.0.2" @@ -26529,14 +24370,10 @@ }, "node_modules/shallowequal": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", "license": "MIT" }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -26547,8 +24384,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", "engines": { "node": ">=8" @@ -26556,8 +24391,6 @@ }, "node_modules/shell-quote": { "version": "1.10.0", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", - "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -26568,8 +24401,6 @@ }, "node_modules/shelljs": { "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", "license": "BSD-3-Clause", "dependencies": { "glob": "^7.0.0", @@ -26583,10 +24414,48 @@ "node": ">=4" } }, + "node_modules/shelljs/node_modules/balanced-match": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/shelljs/node_modules/brace-expansion": { + "version": "1.1.18", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/shelljs/node_modules/glob": { + "version": "7.2.3", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shelljs/node_modules/minimatch": { + "version": "3.1.5", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/should": { "version": "13.2.3", - "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", - "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", "license": "MIT", "dependencies": { "should-equal": "^2.0.0", @@ -26598,8 +24467,6 @@ }, "node_modules/should-equal": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", - "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", "license": "MIT", "dependencies": { "should-type": "^1.4.0" @@ -26607,8 +24474,6 @@ }, "node_modules/should-format": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", - "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", "license": "MIT", "dependencies": { "should-type": "^1.3.0", @@ -26617,14 +24482,10 @@ }, "node_modules/should-type": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", - "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", "license": "MIT" }, "node_modules/should-type-adaptors": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", - "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", "license": "MIT", "dependencies": { "should-type": "^1.3.0", @@ -26633,14 +24494,10 @@ }, "node_modules/should-util": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", - "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", "license": "MIT" }, "node_modules/side-channel": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -26658,8 +24515,6 @@ }, "node_modules/side-channel-list": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -26674,8 +24529,6 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -26692,8 +24545,6 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -26711,8 +24562,6 @@ }, "node_modules/signal-exit": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "license": "ISC", "engines": { "node": ">=14" @@ -26723,8 +24572,6 @@ }, "node_modules/simple-concat": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", "funding": [ { "type": "github", @@ -26743,8 +24590,6 @@ }, "node_modules/simple-get": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", "funding": [ { "type": "github", @@ -26768,8 +24613,6 @@ }, "node_modules/sirv": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", "license": "MIT", "dependencies": { "@polka/url": "^1.0.0-next.24", @@ -26782,14 +24625,10 @@ }, "node_modules/sisteransi": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, "node_modules/sitemap": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.3.tgz", - "integrity": "sha512-tAjEd+wt/YwnEbfNB2ht51ybBJxbEWwe5ki/Z//Wh0rpBFTCUSj46GnxUKEWzhfuJTsee8x3lybHxFgUMig2hw==", "license": "MIT", "dependencies": { "@types/node": "^17.0.5", @@ -26807,14 +24646,10 @@ }, "node_modules/sitemap/node_modules/@types/node": { "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", "license": "MIT" }, "node_modules/skin-tone": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", - "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", "license": "MIT", "dependencies": { "unicode-emoji-modifier-base": "^1.0.0" @@ -26825,8 +24660,6 @@ }, "node_modules/slash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "license": "MIT", "engines": { "node": ">=8" @@ -26834,8 +24667,6 @@ }, "node_modules/slugify": { "version": "1.6.9", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.9.tgz", - "integrity": "sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==", "license": "MIT", "engines": { "node": ">=8.0.0" @@ -26843,8 +24674,6 @@ }, "node_modules/snake-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", "license": "MIT", "dependencies": { "dot-case": "^3.0.4", @@ -26853,8 +24682,6 @@ }, "node_modules/sockjs": { "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", "license": "MIT", "dependencies": { "faye-websocket": "^0.11.3", @@ -26862,10 +24689,13 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/solady": { + "version": "0.0.182", + "dev": true, + "license": "MIT" + }, "node_modules/sort-css-media-queries": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", - "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", "license": "MIT", "engines": { "node": ">= 6.3.0" @@ -26873,8 +24703,6 @@ }, "node_modules/source-map": { "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "license": "BSD-3-Clause", "engines": { "node": ">= 12" @@ -26882,8 +24710,6 @@ }, "node_modules/source-map-js": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -26891,8 +24717,6 @@ }, "node_modules/source-map-support": { "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -26901,8 +24725,6 @@ }, "node_modules/source-map-support/node_modules/source-map": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -26910,18 +24732,23 @@ }, "node_modules/space-separated-tokens": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/spawndamnit": { + "version": "3.0.1", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "cross-spawn": "^7.0.5", + "signal-exit": "^4.0.1" + } + }, "node_modules/spdy": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", "license": "MIT", "dependencies": { "debug": "^4.1.0", @@ -26936,8 +24763,6 @@ }, "node_modules/spdy-transport": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", "license": "MIT", "dependencies": { "debug": "^4.1.0", @@ -26950,8 +24775,6 @@ }, "node_modules/srcset": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", - "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", "license": "MIT", "engines": { "node": ">=12" @@ -26962,8 +24785,6 @@ }, "node_modules/statuses": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -26971,88 +24792,42 @@ }, "node_modules/std-env": { "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "license": "MIT" }, "node_modules/stream-chain": { "version": "2.2.5", - "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", - "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", "license": "BSD-3-Clause" }, "node_modules/stream-json": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", - "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", "license": "BSD-3-Clause", "dependencies": { "stream-chain": "^2.2.5" } }, - "node_modules/strictdom": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strictdom/-/strictdom-1.0.1.tgz", - "integrity": "sha512-cEmp9QeXXRmjj/rVp9oyiqcvyocWab/HaoN4+bwFeZ7QzykJD6L3yD4v12K1x0tHpqRqVpJevN3gW7kyM39Bqg==", - "license": "MIT" - }, "node_modules/string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } }, "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "version": "8.2.2", "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" }, "engines": { - "node": ">=12" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", - "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/stringify-entities": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "license": "MIT", "dependencies": { "character-entities-html4": "^2.0.0", @@ -27065,8 +24840,6 @@ }, "node_modules/stringify-object": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", "license": "BSD-2-Clause", "dependencies": { "get-own-enumerable-property-symbols": "^3.0.0", @@ -27078,21 +24851,28 @@ } }, "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.2.0", "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, "node_modules/strip-bom-string": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -27100,8 +24880,6 @@ }, "node_modules/strip-final-newline": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "license": "MIT", "engines": { "node": ">=6" @@ -27109,8 +24887,6 @@ }, "node_modules/strip-json-comments": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -27118,8 +24894,6 @@ }, "node_modules/stubborn-fs": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz", - "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==", "license": "MIT", "dependencies": { "stubborn-utils": "^1.0.1" @@ -27127,14 +24901,10 @@ }, "node_modules/stubborn-utils": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz", - "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==", "license": "MIT" }, "node_modules/style-to-js": { "version": "1.1.21", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", - "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", "license": "MIT", "dependencies": { "style-to-object": "1.0.14" @@ -27142,8 +24912,6 @@ }, "node_modules/style-to-object": { "version": "1.0.14", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", - "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", "license": "MIT", "dependencies": { "inline-style-parser": "0.2.7" @@ -27151,8 +24919,6 @@ }, "node_modules/stylehacks": { "version": "6.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", - "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", "license": "MIT", "dependencies": { "browserslist": "^4.23.0", @@ -27167,14 +24933,10 @@ }, "node_modules/stylis": { "version": "4.4.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", - "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", "license": "MIT" }, "node_modules/sucrase": { "version": "3.35.1", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", - "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -27195,8 +24957,6 @@ }, "node_modules/sucrase/node_modules/commander": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "license": "MIT", "engines": { "node": ">= 6" @@ -27204,14 +24964,10 @@ }, "node_modules/superstruct": { "version": "0.15.5", - "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.15.5.tgz", - "integrity": "sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==", "license": "MIT" }, "node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -27222,8 +24978,6 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -27234,8 +24988,6 @@ }, "node_modules/svg-parser": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.1.0.tgz", - "integrity": "sha512-bwLf38YmY+TDYHJw1Ex0Co8c4yeXuJAo8YnXGZrscxrvYoVVIvLeniEkV1Ks/54VteMnL4FtyN1+P+TZJdUPmQ==", "license": "MIT", "engines": { "node": ">=8" @@ -27243,8 +24995,6 @@ }, "node_modules/svgo": { "version": "3.3.5", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.5.tgz", - "integrity": "sha512-8SQMzdrvWaD8deUmrnYB+ASyxBVgWUOilg+A75nE/76WdLpj6LopCwiAVvkzkcqy/9b7t2Mg7faFLjg0ZRcZ3w==", "license": "MIT", "dependencies": { "commander": "^7.2.0", @@ -27268,8 +25018,6 @@ }, "node_modules/svgo/node_modules/commander": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", "engines": { "node": ">= 10" @@ -27277,8 +25025,6 @@ }, "node_modules/swagger2openapi": { "version": "7.0.8", - "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", - "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", "license": "BSD-3-Clause", "dependencies": { "call-me-maybe": "^1.0.1", @@ -27302,10 +25048,28 @@ "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, + "node_modules/swagger2openapi/node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/swagger2openapi/node_modules/ansi-styles": { + "version": "4.3.0", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/swagger2openapi/node_modules/cliui": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -27318,14 +25082,10 @@ }, "node_modules/swagger2openapi/node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "node_modules/swagger2openapi/node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -27336,10 +25096,18 @@ "node": ">=8" } }, + "node_modules/swagger2openapi/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/swagger2openapi/node_modules/wrap-ansi": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -27355,8 +25123,6 @@ }, "node_modules/swagger2openapi/node_modules/yaml": { "version": "1.10.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", - "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", "engines": { "node": ">= 6" @@ -27364,8 +25130,6 @@ }, "node_modules/swagger2openapi/node_modules/yargs": { "version": "17.7.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -27382,14 +25146,10 @@ }, "node_modules/tabbable": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", - "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", "license": "MIT" }, "node_modules/tagged-tag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", - "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", "license": "MIT", "engines": { "node": ">=20" @@ -27400,9 +25160,8 @@ }, "node_modules/tailwindcss": { "version": "3.4.4", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", - "integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==", "license": "MIT", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -27437,8 +25196,6 @@ }, "node_modules/tailwindcss-animate": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", - "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", "license": "MIT", "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" @@ -27446,8 +25203,6 @@ }, "node_modules/tailwindcss/node_modules/glob-parent": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -27458,8 +25213,6 @@ }, "node_modules/tailwindcss/node_modules/lilconfig": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", "license": "MIT", "engines": { "node": ">=10" @@ -27467,8 +25220,6 @@ }, "node_modules/tapable": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", - "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "license": "MIT", "engines": { "node": ">=6" @@ -27480,8 +25231,6 @@ }, "node_modules/tar": { "version": "7.5.22", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", - "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -27497,8 +25246,6 @@ }, "node_modules/tar-fs": { "version": "2.1.5", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", - "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", "license": "MIT", "dependencies": { "chownr": "^1.1.1", @@ -27509,14 +25256,10 @@ }, "node_modules/tar-fs/node_modules/chownr": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "license": "ISC" }, "node_modules/tar-stream": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "license": "MIT", "dependencies": { "bl": "^4.0.3", @@ -27529,20 +25272,19 @@ "node": ">=6" } }, - "node_modules/tar/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "node_modules/term-size": { + "version": "2.2.1", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/terser": { - "version": "5.51.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz", - "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", + "version": "5.50.0", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -27559,8 +25301,6 @@ }, "node_modules/terser-webpack-plugin": { "version": "5.6.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", - "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", @@ -27619,8 +25359,6 @@ }, "node_modules/terser-webpack-plugin/node_modules/jest-worker": { "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -27633,8 +25371,6 @@ }, "node_modules/terser-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -27648,20 +25384,14 @@ }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, "node_modules/teslabot": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/teslabot/-/teslabot-1.5.0.tgz", - "integrity": "sha512-e2MmELhCgrgZEGo7PQu/6bmYG36IDH+YrBI1iGm6jovXkeDIGa3pZ2WSqRjzkuw2vt1EqfkZoV5GpXgqL8QJVg==", "license": "MIT" }, "node_modules/test-exclude": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", - "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", "dev": true, "license": "ISC", "dependencies": { @@ -27673,49 +25403,11 @@ "node": "20 || >=22" } }, - "node_modules/test-exclude/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/text-encoding-utf-8": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", - "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" + "version": "1.0.2" }, "node_modules/thenify": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "license": "MIT", "dependencies": { "any-promise": "^1.0.0" @@ -27723,8 +25415,6 @@ }, "node_modules/thenify-all": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -27735,8 +25425,6 @@ }, "node_modules/thingies": { "version": "2.6.1", - "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.1.tgz", - "integrity": "sha512-cV/CMGTK3M4MlnJ/0At6ismOw/A0EEniDNScajjz/Br3c1sqE72YD01rGpPTKwd27wAxI5Pr+6+0w8yofzFRYw==", "license": "MIT", "engines": { "node": ">=10.18" @@ -27751,26 +25439,18 @@ }, "node_modules/thunky": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "license": "MIT" }, "node_modules/tiny-invariant": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, "node_modules/tiny-warning": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", - "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "version": "1.3.0", "license": "MIT", "engines": { "node": ">=18" @@ -27778,8 +25458,6 @@ }, "node_modules/tinyglobby": { "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -27794,8 +25472,6 @@ }, "node_modules/tinyglobby/node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", "engines": { "node": ">=12.0.0" @@ -27811,9 +25487,8 @@ }, "node_modules/tinyglobby/node_modules/picomatch": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", - "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -27823,17 +25498,24 @@ }, "node_modules/tinypool": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" } }, + "node_modules/tmp": { + "version": "0.0.33", + "dev": true, + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -27844,8 +25526,6 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", "engines": { "node": ">=0.6" @@ -27853,14 +25533,10 @@ }, "node_modules/toml": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", - "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", "license": "MIT" }, "node_modules/totalist": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "license": "MIT", "engines": { "node": ">=6" @@ -27868,14 +25544,10 @@ }, "node_modules/tr46": { "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, "node_modules/tree-dump": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", - "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", "license": "Apache-2.0", "engines": { "node": ">=10.0" @@ -27890,8 +25562,6 @@ }, "node_modules/trim-lines": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", "license": "MIT", "funding": { "type": "github", @@ -27900,8 +25570,6 @@ }, "node_modules/trough": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "license": "MIT", "funding": { "type": "github", @@ -27910,14 +25578,10 @@ }, "node_modules/ts-algebra": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-1.2.2.tgz", - "integrity": "sha512-kloPhf1hq3JbCPOTYoOWDKxebWjNb2o/LKnNfkWhxVVisFFmMJPPdJeGoGmM+iRLyoXAR61e08Pb+vUXINg8aA==", "license": "MIT" }, "node_modules/ts-dedent": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", - "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", "license": "MIT", "engines": { "node": ">=6.10" @@ -27925,15 +25589,12 @@ }, "node_modules/ts-interface-checker": { "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "license": "Apache-2.0" }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsx": { "version": "4.23.13", @@ -27956,8 +25617,6 @@ }, "node_modules/tsyringe": { "version": "4.10.0", - "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", - "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", "license": "MIT", "dependencies": { "tslib": "^1.9.3" @@ -27968,14 +25627,10 @@ }, "node_modules/tsyringe/node_modules/tslib": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "license": "0BSD" }, "node_modules/tunnel-agent": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" @@ -27986,14 +25641,18 @@ }, "node_modules/tweetnacl": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", "license": "Unlicense" }, + "node_modules/type-detect": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.9.0.tgz", - "integrity": "sha512-yANm3Jr3GiJ1qgJlxGAVxTOIcEOk1rhQHamlXtnrCK7EHP4HeM9OGxtMg/W7HFdrVzw/ZWJKGVIJusVH85sLtw==", + "version": "5.8.0", "license": "(MIT OR CC0-1.0)", "dependencies": { "tagged-tag": "^1.0.0" @@ -28007,8 +25666,6 @@ }, "node_modules/type-is": { "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "license": "MIT", "dependencies": { "media-typer": "0.3.0", @@ -28020,8 +25677,6 @@ }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "license": "MIT", "dependencies": { "is-typedarray": "^1.0.0" @@ -28029,8 +25684,6 @@ }, "node_modules/typedoc": { "version": "0.28.20", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.20.tgz", - "integrity": "sha512-uSKqkh8Cr48vllnEy+jdaAgOeR6Y+QCBW7usgUsKj7gJEfR7stw9U/fE49LBnj2tPRKPY0c0EBJSWe9Appmplg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -28053,8 +25706,6 @@ }, "node_modules/typedoc-docusaurus-theme": { "version": "1.4.2", - "resolved": "https://registry.npmjs.org/typedoc-docusaurus-theme/-/typedoc-docusaurus-theme-1.4.2.tgz", - "integrity": "sha512-i9YYDcScLD0WUiX8I+LXHX3ZVvRDlJsmRo9l/uWrFT37cHlMz4Ay0GOnWzHUBnnwAo1uzYOw9RjUXznbWozBEA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -28063,10 +25714,9 @@ }, "node_modules/typedoc-plugin-markdown": { "version": "4.13.0", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.13.0.tgz", - "integrity": "sha512-OHaOLoMTS0wL2ud73WXvxv486mrUEtgXxW4iKiTOGJVipT/VBWY76rlVRMQGes7aVonoIf5roWRe1UvHbufLlQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 18" }, @@ -28074,28 +25724,11 @@ "typedoc": "0.28.x" } }, - "node_modules/typedoc/node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/typescript": { "name": "@typescript/typescript6", "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript6/-/typescript6-6.0.2.tgz", - "integrity": "sha512-mbCddXd+jm7hfx7w2YU64/Av4/NqqeG3GoRZgxPcgoTxYjhrcfJRw9ULch71SS4G+Q3bOXFhRvPqjguN0Hyp5w==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@typescript/old": "npm:typescript@^6" }, @@ -28105,8 +25738,6 @@ }, "node_modules/uc.micro": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true, "license": "MIT" }, @@ -28124,8 +25755,6 @@ }, "node_modules/undici": { "version": "8.10.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz", - "integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==", "license": "MIT", "engines": { "node": ">=22.19.0" @@ -28133,14 +25762,10 @@ }, "node_modules/undici-types": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "license": "MIT", "engines": { "node": ">=4" @@ -28148,8 +25773,6 @@ }, "node_modules/unicode-emoji-modifier-base": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", - "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", "license": "MIT", "engines": { "node": ">=4" @@ -28157,8 +25780,6 @@ }, "node_modules/unicode-match-property-ecmascript": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", @@ -28170,8 +25791,6 @@ }, "node_modules/unicode-match-property-value-ecmascript": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", - "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "license": "MIT", "engines": { "node": ">=4" @@ -28179,8 +25798,6 @@ }, "node_modules/unicode-property-aliases-ecmascript": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", - "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "license": "MIT", "engines": { "node": ">=4" @@ -28188,8 +25805,6 @@ }, "node_modules/unicorn-magic": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, "license": "MIT", "engines": { @@ -28201,8 +25816,6 @@ }, "node_modules/unified": { "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -28220,8 +25833,6 @@ }, "node_modules/unique-string": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", - "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", "license": "MIT", "dependencies": { "crypto-random-string": "^4.0.0" @@ -28235,8 +25846,6 @@ }, "node_modules/unist-util-is": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -28248,8 +25857,6 @@ }, "node_modules/unist-util-position": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -28261,8 +25868,6 @@ }, "node_modules/unist-util-position-from-estree": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", - "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -28274,8 +25879,6 @@ }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -28287,8 +25890,6 @@ }, "node_modules/unist-util-visit": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -28302,8 +25903,6 @@ }, "node_modules/unist-util-visit-parents": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -28316,8 +25915,6 @@ }, "node_modules/universalify": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "license": "MIT", "engines": { "node": ">= 10.0.0" @@ -28325,17 +25922,13 @@ }, "node_modules/unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/update-browserslist-db": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", - "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "version": "1.3.1", "funding": [ { "type": "opencollective", @@ -28364,8 +25957,6 @@ }, "node_modules/update-notifier": { "version": "7.3.1", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-7.3.1.tgz", - "integrity": "sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==", "license": "BSD-2-Clause", "dependencies": { "boxen": "^8.0.1", @@ -28386,34 +25977,8 @@ "url": "https://github.com/yeoman/update-notifier?sponsor=1" } }, - "node_modules/update-notifier/node_modules/ansi-regex": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", - "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/update-notifier/node_modules/boxen": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", - "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", "license": "MIT", "dependencies": { "ansi-align": "^3.0.1", @@ -28434,8 +25999,6 @@ }, "node_modules/update-notifier/node_modules/camelcase": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", "license": "MIT", "engines": { "node": ">=16" @@ -28446,8 +26009,6 @@ }, "node_modules/update-notifier/node_modules/chalk": { "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -28456,16 +26017,8 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/update-notifier/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, "node_modules/update-notifier/node_modules/string-width": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -28479,25 +26032,8 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/update-notifier/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/update-notifier/node_modules/type-fest": { "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" @@ -28508,8 +26044,6 @@ }, "node_modules/update-notifier/node_modules/widest-line": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", - "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", "license": "MIT", "dependencies": { "string-width": "^7.0.0" @@ -28521,27 +26055,8 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/update-notifier/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/uri-js": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -28549,8 +26064,6 @@ }, "node_modules/url": { "version": "0.11.4", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", - "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", "license": "MIT", "dependencies": { "punycode": "^1.4.1", @@ -28562,8 +26075,6 @@ }, "node_modules/url-loader": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", - "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", "license": "MIT", "dependencies": { "loader-utils": "^2.0.0", @@ -28589,9 +26100,8 @@ }, "node_modules/url-loader/node_modules/ajv": { "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -28605,8 +26115,6 @@ }, "node_modules/url-loader/node_modules/ajv-keywords": { "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" @@ -28614,14 +26122,10 @@ }, "node_modules/url-loader/node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "license": "MIT" }, "node_modules/url-loader/node_modules/schema-utils": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.8", @@ -28638,14 +26142,10 @@ }, "node_modules/url/node_modules/punycode": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "license": "MIT" }, "node_modules/usb": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/usb/-/usb-2.9.0.tgz", - "integrity": "sha512-G0I/fPgfHUzWH8xo2KkDxTTFruUWfppgSFJ+bQxz/kVY2x15EQ/XDB7dqD1G432G4gBG4jYQuF3U7j/orSs5nw==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -28659,14 +26159,10 @@ }, "node_modules/usb/node_modules/node-addon-api": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", "license": "MIT" }, "node_modules/usb/node_modules/node-gyp-build": { "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "license": "MIT", "bin": { "node-gyp-build": "bin.js", @@ -28676,8 +26172,6 @@ }, "node_modules/use-editable": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/use-editable/-/use-editable-2.3.3.tgz", - "integrity": "sha512-7wVD2JbfAFJ3DK0vITvXBdpd9JAz5BcKAAolsnLBuBn6UDDwBGuCIAGvR3yA2BNKm578vAMVHFCWaOcA+BhhiA==", "license": "MIT", "peerDependencies": { "react": ">= 16.8.0" @@ -28685,8 +26179,6 @@ }, "node_modules/use-sync-external-store": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -28694,8 +26186,6 @@ }, "node_modules/utf-8-validate": { "version": "6.0.6", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.6.tgz", - "integrity": "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==", "hasInstallScript": true, "license": "MIT", "optional": true, @@ -28708,20 +26198,14 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, "node_modules/utila": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", "license": "MIT" }, "node_modules/utility-types": { "version": "3.11.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", - "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", "license": "MIT", "engines": { "node": ">= 4" @@ -28729,8 +26213,6 @@ }, "node_modules/utils-merge": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", "engines": { "node": ">= 0.4.0" @@ -28738,8 +26220,6 @@ }, "node_modules/uuid": { "version": "11.1.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", - "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -28751,8 +26231,6 @@ }, "node_modules/v8-to-istanbul": { "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "license": "ISC", "dependencies": { @@ -28766,8 +26244,6 @@ }, "node_modules/valibot": { "version": "1.4.2", - "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.4.2.tgz", - "integrity": "sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==", "license": "MIT", "peerDependencies": { "typescript": ">=5" @@ -28780,47 +26256,33 @@ }, "node_modules/validate.io-array": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz", - "integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==", "license": "MIT" }, "node_modules/validate.io-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz", - "integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==" + "version": "1.0.2" }, "node_modules/validate.io-integer": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz", - "integrity": "sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==", "dependencies": { "validate.io-number": "^1.0.3" } }, "node_modules/validate.io-integer-array": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz", - "integrity": "sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==", "dependencies": { "validate.io-array": "^1.0.3", "validate.io-integer": "^1.0.4" } }, "node_modules/validate.io-number": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz", - "integrity": "sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg==" + "version": "1.0.3" }, "node_modules/value-equal": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", "license": "MIT" }, "node_modules/vary": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -28828,8 +26290,6 @@ }, "node_modules/vfile": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -28842,8 +26302,6 @@ }, "node_modules/vfile-location": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", - "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -28856,8 +26314,6 @@ }, "node_modules/vfile-message": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -28869,9 +26325,7 @@ } }, "node_modules/viem": { - "version": "2.56.3", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.56.3.tgz", - "integrity": "sha512-vUObq3GO7D3lz9gPNEE/xd5jIUnMbeVDWewh252o54pDEDlW4pDe6FEd/qTGL5RyK52cGHMM7kQ3wjxhilEvkQ==", + "version": "2.55.19", "dev": true, "funding": [ { @@ -28887,7 +26341,7 @@ "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", - "ox": "0.14.44", + "ox": "0.14.34", "ws": "8.21.0" }, "peerDependencies": { @@ -28901,8 +26355,6 @@ }, "node_modules/viem/node_modules/@noble/curves": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", - "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", "dev": true, "license": "MIT", "dependencies": { @@ -28917,8 +26369,6 @@ }, "node_modules/viem/node_modules/@noble/hashes": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, "license": "MIT", "engines": { @@ -28930,8 +26380,6 @@ }, "node_modules/viem/node_modules/abitype": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", - "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", "dev": true, "license": "MIT", "funding": { @@ -28952,8 +26400,6 @@ }, "node_modules/warning": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", "license": "MIT", "dependencies": { "loose-envify": "^1.0.0" @@ -28961,8 +26407,6 @@ }, "node_modules/watchpack": { "version": "2.5.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", - "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", "license": "MIT", "dependencies": { "graceful-fs": "^4.1.2" @@ -28973,8 +26417,6 @@ }, "node_modules/wbuf": { "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", "license": "MIT", "dependencies": { "minimalistic-assert": "^1.0.0" @@ -28982,8 +26424,6 @@ }, "node_modules/web-namespaces": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", "license": "MIT", "funding": { "type": "github", @@ -28992,15 +26432,12 @@ }, "node_modules/webidl-conversions": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "license": "BSD-2-Clause" }, "node_modules/webpack": { - "version": "5.110.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.110.3.tgz", - "integrity": "sha512-GuizBzRvo9YPpyoNMf3ag7AzxbaW85qrRSqTha345KyJbAFPt3/cMzBM0h+RWg7SK/7DdzRLINP3LvQ0hvr4hg==", + "version": "5.109.2", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", @@ -29012,10 +26449,11 @@ "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.24.4", "es-module-lexer": "^2.1.0", + "eslint-scope": "5.1.1", "events": "^3.2.0", "graceful-fs": "^4.2.11", "mime-db": "^1.54.0", - "minimizer-webpack-plugin": "^5.7.0", + "minimizer-webpack-plugin": "^5.6.1", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", @@ -29040,8 +26478,6 @@ }, "node_modules/webpack-bundle-analyzer": { "version": "4.10.2", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", - "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", "license": "MIT", "dependencies": { "@discoveryjs/json-ext": "0.5.7", @@ -29066,17 +26502,13 @@ }, "node_modules/webpack-bundle-analyzer/node_modules/commander": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", "engines": { "node": ">= 10" } }, "node_modules/webpack-dev-middleware": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.6.tgz", - "integrity": "sha512-yBWCMvIfUmuhAE8vdqUKzH0vg9kuWN0KeG4vBnqRplUFHRU7lMQjkiJWxVQzvo2BTewqhPhDlMB41rAt2jVA9A==", + "version": "7.4.5", "license": "MIT", "dependencies": { "colorette": "^2.0.10", @@ -29104,14 +26536,10 @@ }, "node_modules/webpack-dev-middleware/node_modules/colorette": { "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "license": "MIT" }, "node_modules/webpack-dev-middleware/node_modules/mime-db": { "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -29119,8 +26547,6 @@ }, "node_modules/webpack-dev-middleware/node_modules/mime-types": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -29135,8 +26561,6 @@ }, "node_modules/webpack-dev-middleware/node_modules/range-parser": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", - "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -29148,8 +26572,6 @@ }, "node_modules/webpack-dev-server": { "version": "5.2.6", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.6.tgz", - "integrity": "sha512-HNLRmamRvVavZQ+avceZifmv8hmdUjg43t6MI4SqJDwFdW7RPQwH5vzGhDRZSX59SgfbeHhLnq3g+uooWo7pVw==", "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.13", @@ -29205,8 +26627,6 @@ }, "node_modules/webpack-dev-server/node_modules/@types/ws": { "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -29214,14 +26634,10 @@ }, "node_modules/webpack-dev-server/node_modules/colorette": { "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "license": "MIT" }, "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "license": "MIT", "engines": { "node": ">=12" @@ -29232,8 +26648,6 @@ }, "node_modules/webpack-dev-server/node_modules/open": { "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", "license": "MIT", "dependencies": { "default-browser": "^5.2.1", @@ -29250,8 +26664,6 @@ }, "node_modules/webpack-merge": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", - "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", @@ -29264,8 +26676,6 @@ }, "node_modules/webpack-sources": { "version": "3.5.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", - "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", "license": "MIT", "engines": { "node": ">=10.13.0" @@ -29273,8 +26683,6 @@ }, "node_modules/webpack/node_modules/mime-db": { "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -29282,8 +26690,6 @@ }, "node_modules/webpackbar": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-7.0.0.tgz", - "integrity": "sha512-aS9soqSO2iCHgqHoCrj4LbfGQUboDCYJPSFOAchEK+9psIjNrfSWW4Y0YEz67MKURNvMmfo0ycOg9d/+OOf9/Q==", "license": "MIT", "dependencies": { "ansis": "^3.2.0", @@ -29309,8 +26715,6 @@ }, "node_modules/websocket-driver": { "version": "0.7.5", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz", - "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", @@ -29323,8 +26727,6 @@ }, "node_modules/websocket-extensions": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "license": "Apache-2.0", "engines": { "node": ">=0.8.0" @@ -29332,9 +26734,6 @@ }, "node_modules/whatwg-encoding": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", "license": "MIT", "dependencies": { "iconv-lite": "0.6.3" @@ -29345,8 +26744,6 @@ }, "node_modules/whatwg-encoding/node_modules/iconv-lite": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -29357,8 +26754,6 @@ }, "node_modules/whatwg-mimetype": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "license": "MIT", "engines": { "node": ">=18" @@ -29366,8 +26761,6 @@ }, "node_modules/whatwg-url": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "license": "MIT", "dependencies": { "tr46": "~0.0.3", @@ -29376,14 +26769,10 @@ }, "node_modules/when-exit": { "version": "2.1.5", - "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz", - "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==", "license": "MIT" }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -29397,8 +26786,6 @@ }, "node_modules/widest-line": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", - "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", "license": "MIT", "dependencies": { "string-width": "^5.0.1" @@ -29410,78 +26797,65 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "9.2.2", "license": "MIT" }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/widest-line/node_modules/string-width": { + "version": "5.1.2", "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" }, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", - "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } + "node_modules/wildcard": { + "version": "2.0.1", + "license": "MIT" }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/wrap-ansi": { + "version": "9.0.2", "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { + "node_modules/wrap-ansi/node_modules/string-width": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.2.2" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, "node_modules/write-file-atomic": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", @@ -29492,15 +26866,12 @@ }, "node_modules/write-file-atomic/node_modules/signal-exit": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, "node_modules/ws": { "version": "8.21.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", - "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -29519,8 +26890,6 @@ }, "node_modules/wsl-utils": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", "license": "MIT", "dependencies": { "is-wsl": "^3.1.0" @@ -29534,8 +26903,6 @@ }, "node_modules/wsl-utils/node_modules/is-wsl": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", - "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "license": "MIT", "dependencies": { "is-inside-container": "^1.0.0" @@ -29549,8 +26916,6 @@ }, "node_modules/xdg-basedir": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", "license": "MIT", "engines": { "node": ">=12" @@ -29561,8 +26926,6 @@ }, "node_modules/xml-formatter": { "version": "3.7.0", - "resolved": "https://registry.npmjs.org/xml-formatter/-/xml-formatter-3.7.0.tgz", - "integrity": "sha512-+8qTc3zv2UcJ1v9IsSIce37Dl4MQG14Cp7tWrwmy202UaI1wqRukw5QMX1JHsV+DX64yw77EgGsj2s5wGvuMbQ==", "license": "MIT", "dependencies": { "xml-parser-xo": "^4.1.5" @@ -29573,8 +26936,6 @@ }, "node_modules/xml-js": { "version": "1.6.11", - "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", - "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", "license": "MIT", "dependencies": { "sax": "^1.2.4" @@ -29585,8 +26946,6 @@ }, "node_modules/xml-parser-xo": { "version": "4.1.6", - "resolved": "https://registry.npmjs.org/xml-parser-xo/-/xml-parser-xo-4.1.6.tgz", - "integrity": "sha512-mXbSNSM+rIwndoxSwmFa83bOdeP8G/cOGr7XvxA67X7ebCzoAuVez9JYDCDub3zRDNBZxIbfjuXLSsamr7NfwQ==", "license": "MIT", "engines": { "node": ">= 20" @@ -29594,23 +26953,21 @@ }, "node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "license": "ISC" + "version": "5.0.0", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/yaml": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -29624,14 +26981,10 @@ }, "node_modules/yaml-ast-parser": { "version": "0.0.43", - "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", - "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", "license": "Apache-2.0" }, "node_modules/yargs": { "version": "18.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", - "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", "license": "MIT", "dependencies": { "cliui": "^9.0.1", @@ -29647,60 +27000,13 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "license": "ISC", "engines": { "node": ">=12" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", - "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", - "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/yargs/node_modules/yargs-parser": { "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "license": "ISC", "engines": { "node": "^20.19.0 || ^22.12.0 || >=23" @@ -29708,8 +27014,6 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { @@ -29721,8 +27025,6 @@ }, "node_modules/yoctocolors": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", - "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", "dev": true, "license": "MIT", "engines": { @@ -29734,8 +27036,6 @@ }, "node_modules/zod": { "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -29743,8 +27043,6 @@ }, "node_modules/zwitch": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", "license": "MIT", "funding": { "type": "github", diff --git a/package.json b/package.json index c93ed80f5..43c253aae 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "prepare": "npm run build" }, "devDependencies": { + "@chainlink/contracts-ccip": "2.0.0", "@types/node": "26.4.1", "@typescript/native": "npm:typescript@7.0.2", "brace-expansion": "5.0.9",