diff --git a/packages/webapp/jest.config.ts b/packages/webapp/jest.config.ts new file mode 100644 index 00000000..9ba51b39 --- /dev/null +++ b/packages/webapp/jest.config.ts @@ -0,0 +1,9 @@ +import type {Config} from 'jest'; + +const config: Config = { + preset: 'ts-jest', + testEnvironment: 'node', + verbose: true +}; + +export default config; \ No newline at end of file diff --git a/packages/webapp/package.json b/packages/webapp/package.json index f9c07c9f..6db0f4a3 100644 --- a/packages/webapp/package.json +++ b/packages/webapp/package.json @@ -7,6 +7,9 @@ "path": false, "os": false }, + "scripts":{ + "test": "jest" + }, "dependencies": { "@dnd-kit/core": "^6.0.8", "@dnd-kit/sortable": "^7.0.2", @@ -35,6 +38,10 @@ "wagmi": "^1.4.5" }, "devDependencies": { + "jest":"29.7.0", + "@jest/globals": "^29.7.0", + "ts-jest": "29.1.2", + "ts-node": "10.9.2", "@swc-jotai/debug-label": "^0.1.0", "@swc-jotai/react-refresh": "^0.1.0", "@types/eslint": "^8.44.6", diff --git a/packages/webapp/src/store/subscriptions.ts b/packages/webapp/src/store/subscriptions.ts index 4abde7cc..b019b211 100644 --- a/packages/webapp/src/store/subscriptions.ts +++ b/packages/webapp/src/store/subscriptions.ts @@ -11,6 +11,7 @@ // ================================================================================================= +import _ from "lodash" import { Log } from "viem" import { getPublicClient } from "wagmi/actions" @@ -51,8 +52,8 @@ const eventNames = [ /** ID of the game we are currently subscribed to, or null if we are not subscribed. */ let currentlySubscribedID: bigint|null = null -/** List of function to call to unsubscribe from game updates. */ -let unsubFunctions: (() => void)[] = [] +/** Function to call to unsubscribe from game updates. */ +let unsubscribeEventListener: (() => void) | null = null // ------------------------------------------------------------------------------------------------- @@ -68,21 +69,37 @@ export function subscribeToGame(ID: bigint|null) { if (needsUnsub) { // remove subscription - unsubFunctions.forEach(unsub => unsub()) - unsubFunctions = [] + if(unsubscribeEventListener){ + unsubscribeEventListener() + unsubscribeEventListener = null; + } console.log(`unsubscribed from game events for game ID ${currentlySubscribedID}`) currentlySubscribedID = null } if (needsSub) { currentlySubscribedID = ID - eventNames.forEach(eventName => { - unsubFunctions.push(publicClient.watchContractEvent({ - address: deployment.Game, - abi: gameABI, - eventName: eventName as any, - args: { gameID: ID }, - onLogs: logs => gameEventListener(eventName, logs) - })) + + const eventsABI = gameABI.filter((abi) => abi.type === "event" && eventNames.includes(abi.name)); + + /** + * Listen to all events in eventNames for the current game ID. + * All of these events must have an indexed gameID argument. + * We must use watchEvent to be able to listen to multiple events at the same time. + * Wagmi does not officially support listening to multiple events with an argument filter, + * and this might break in future updates. + */ + unsubscribeEventListener = publicClient.watchEvent({ + address: deployment.Game, + events: eventsABI, + args: { gameID: ID }, + onLogs: logs => { + Object.entries(_.groupBy(logs, (log:any) => log.eventName)) + .forEach( + ([eventName, logs]:[string,any]) => { + gameEventListener(eventName, logs) + } + ) + } }) } } diff --git a/packages/webapp/src/utils/generated-abi.ts b/packages/webapp/src/utils/generated-abi.ts new file mode 100644 index 00000000..d116d07a --- /dev/null +++ b/packages/webapp/src/utils/generated-abi.ts @@ -0,0 +1,1376 @@ +export const gameABI = [ + { + stateMutability: 'nonpayable', + type: 'constructor', + inputs: [ + { + name: 'inventory_', + internalType: 'contract Inventory', + type: 'address', + }, + { + name: 'drawVerifier_', + internalType: 'contract Groth16Verifier', + type: 'address', + }, + { + name: 'playVerifier_', + internalType: 'contract Groth16Verifier', + type: 'address', + }, + { + name: 'drawHandVerifier_', + internalType: 'contract Groth16Verifier', + type: 'address', + }, + { name: 'checkProofs_', internalType: 'bool', type: 'bool' }, + { name: 'noRandom_', internalType: 'bool', type: 'bool' }, + ], + }, + { type: 'error', inputs: [], name: 'AlreadyDrew' }, + { type: 'error', inputs: [], name: 'AlreadyJoined' }, + { type: 'error', inputs: [], name: 'CardIndexTooHigh' }, + { type: 'error', inputs: [], name: 'FalseStart' }, + { type: 'error', inputs: [], name: 'GameAlreadyEnded' }, + { type: 'error', inputs: [], name: 'GameAlreadyLocked' }, + { type: 'error', inputs: [], name: 'GameIsFull' }, + { type: 'error', inputs: [], name: 'ImplementationError' }, + { type: 'error', inputs: [], name: 'InvalidProof' }, + { type: 'error', inputs: [], name: 'NoGameNoLife' }, + { type: 'error', inputs: [], name: 'NotAllowedToJoin' }, + { type: 'error', inputs: [], name: 'OvereagerCanceller' }, + { type: 'error', inputs: [], name: 'OvereagerCreator' }, + { type: 'error', inputs: [], name: 'PlayerNotInGame' }, + { type: 'error', inputs: [], name: 'WrongPlayer' }, + { type: 'error', inputs: [], name: 'WrongStep' }, + { type: 'error', inputs: [], name: 'YoullNeverPlayAlone' }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + { name: 'player', internalType: 'uint8', type: 'uint8', indexed: false }, + ], + name: 'CardDrawn', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + { name: 'player', internalType: 'uint8', type: 'uint8', indexed: false }, + { + name: 'card', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + ], + name: 'CardPlayed', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + { + name: 'player', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'Champion', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + { + name: 'player', + internalType: 'address', + type: 'address', + indexed: false, + }, + { + name: 'cardIndex', + internalType: 'uint8', + type: 'uint8', + indexed: false, + }, + ], + name: 'CreatureDestroyed', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + ], + name: 'FullHouse', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + ], + name: 'GameCancelled', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: false, + }, + { + name: 'creator', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'GameCreated', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + ], + name: 'GameStarted', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + ], + name: 'MissingPlayers', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + { + name: 'attackingPlayer', + internalType: 'address', + type: 'address', + indexed: false, + }, + { + name: 'defendingPlayer', + internalType: 'address', + type: 'address', + indexed: false, + }, + ], + name: 'PlayerAttacked', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + { + name: 'player', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'PlayerConceded', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + { + name: 'player', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'PlayerDefeated', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + { + name: 'attackingPlayer', + internalType: 'address', + type: 'address', + indexed: false, + }, + { + name: 'defendingPlayer', + internalType: 'address', + type: 'address', + indexed: false, + }, + ], + name: 'PlayerDefended', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + { + name: 'player', + internalType: 'address', + type: 'address', + indexed: false, + }, + ], + name: 'PlayerDrewHand', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + { + name: 'player', + internalType: 'address', + type: 'address', + indexed: false, + }, + ], + name: 'PlayerJoined', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + { + name: 'player', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'PlayerTimedOut', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'gameID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + { + name: 'player', + internalType: 'address', + type: 'address', + indexed: false, + }, + ], + name: 'TurnEnded', + }, + { + stateMutability: 'pure', + type: 'function', + inputs: [ + { name: '', internalType: 'uint256', type: 'uint256' }, + { name: '', internalType: 'address', type: 'address' }, + { name: '', internalType: 'uint8', type: 'uint8' }, + { name: '', internalType: 'bytes', type: 'bytes' }, + ], + name: 'allowAnyPlayerAndDeck', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'gameID', internalType: 'uint256', type: 'uint256' }, + { name: 'targetPlayer', internalType: 'uint8', type: 'uint8' }, + { name: 'attacking', internalType: 'uint8[]', type: 'uint8[]' }, + ], + name: 'attack', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [{ name: 'gameID', internalType: 'uint256', type: 'uint256' }], + name: 'cancelGame', + outputs: [], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'cardsCollection', + outputs: [ + { name: '', internalType: 'contract CardsCollection', type: 'address' }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [{ name: 'gameID', internalType: 'uint256', type: 'uint256' }], + name: 'concedeGame', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [{ name: 'numberOfPlayers', internalType: 'uint8', type: 'uint8' }], + name: 'createGame', + outputs: [{ name: 'gameID', internalType: 'uint256', type: 'uint256' }], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'gameID', internalType: 'uint256', type: 'uint256' }, + { name: 'defending', internalType: 'uint8[]', type: 'uint8[]' }, + ], + name: 'defend', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'gameID', internalType: 'uint256', type: 'uint256' }, + { name: 'handRoot', internalType: 'bytes32', type: 'bytes32' }, + { name: 'deckRoot', internalType: 'bytes32', type: 'bytes32' }, + { name: 'proofA', internalType: 'uint256[2]', type: 'uint256[2]' }, + { name: 'proofB', internalType: 'uint256[2][2]', type: 'uint256[2][2]' }, + { name: 'proofC', internalType: 'uint256[2]', type: 'uint256[2]' }, + ], + name: 'drawCard', + outputs: [], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'drawHandVerifier', + outputs: [ + { name: '', internalType: 'contract Groth16Verifier', type: 'address' }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'gameID', internalType: 'uint256', type: 'uint256' }, + { name: 'handRoot', internalType: 'bytes32', type: 'bytes32' }, + { name: 'deckRoot', internalType: 'bytes32', type: 'bytes32' }, + { name: 'proofA', internalType: 'uint256[2]', type: 'uint256[2]' }, + { name: 'proofB', internalType: 'uint256[2][2]', type: 'uint256[2][2]' }, + { name: 'proofC', internalType: 'uint256[2]', type: 'uint256[2]' }, + ], + name: 'drawInitialHand', + outputs: [], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'drawVerifier', + outputs: [ + { name: '', internalType: 'contract Groth16Verifier', type: 'address' }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [{ name: 'gameID', internalType: 'uint256', type: 'uint256' }], + name: 'endTurn', + outputs: [], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [ + { name: 'gameID', internalType: 'uint256', type: 'uint256' }, + { name: 'player', internalType: 'address', type: 'address' }, + { name: 'fetchCards', internalType: 'bool', type: 'bool' }, + ], + name: 'fetchGameData', + outputs: [ + { + name: '', + internalType: 'struct FetchedGameData', + type: 'tuple', + components: [ + { name: 'gameID', internalType: 'uint256', type: 'uint256' }, + { name: 'gameCreator', internalType: 'address', type: 'address' }, + { name: 'players', internalType: 'address[]', type: 'address[]' }, + { + name: 'playerData', + internalType: 'struct PlayerData[]', + type: 'tuple[]', + components: [ + { name: 'health', internalType: 'uint16', type: 'uint16' }, + { name: 'defeated', internalType: 'bool', type: 'bool' }, + { name: 'deckStart', internalType: 'uint8', type: 'uint8' }, + { name: 'deckEnd', internalType: 'uint8', type: 'uint8' }, + { name: 'handSize', internalType: 'uint8', type: 'uint8' }, + { name: 'deckSize', internalType: 'uint8', type: 'uint8' }, + { + name: 'joinBlockNum', + internalType: 'uint256', + type: 'uint256', + }, + { name: 'saltHash', internalType: 'uint256', type: 'uint256' }, + { name: 'handRoot', internalType: 'bytes32', type: 'bytes32' }, + { name: 'deckRoot', internalType: 'bytes32', type: 'bytes32' }, + { name: 'battlefield', internalType: 'uint256', type: 'uint256' }, + { name: 'graveyard', internalType: 'uint256', type: 'uint256' }, + { name: 'attacking', internalType: 'uint8[]', type: 'uint8[]' }, + ], + }, + { name: 'lastBlockNum', internalType: 'uint256', type: 'uint256' }, + { + name: 'publicRandomness', + internalType: 'uint256', + type: 'uint256', + }, + { name: 'playersLeftToJoin', internalType: 'uint8', type: 'uint8' }, + { name: 'livePlayers', internalType: 'uint8[]', type: 'uint8[]' }, + { name: 'currentPlayer', internalType: 'uint8', type: 'uint8' }, + { name: 'currentStep', internalType: 'enum GameStep', type: 'uint8' }, + { name: 'attackingPlayer', internalType: 'address', type: 'address' }, + { name: 'cards', internalType: 'uint256[]', type: 'uint256[]' }, + ], + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + name: 'gameData', + outputs: [ + { name: 'gameCreator', internalType: 'address', type: 'address' }, + { name: 'lastBlockNum', internalType: 'uint256', type: 'uint256' }, + { name: 'playersLeftToJoin', internalType: 'uint8', type: 'uint8' }, + { + name: 'joinCheck', + internalType: + 'function (uint256,address,uint8,bytes) external returns (bool)', + type: 'function', + }, + { name: 'currentPlayer', internalType: 'uint8', type: 'uint8' }, + { name: 'currentStep', internalType: 'enum GameStep', type: 'uint8' }, + { name: 'attackingPlayer', internalType: 'address', type: 'address' }, + ], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [{ name: 'gameID', internalType: 'uint256', type: 'uint256' }], + name: 'getCards', + outputs: [{ name: '', internalType: 'uint256[]', type: 'uint256[]' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [ + { name: 'gameID', internalType: 'uint256', type: 'uint256' }, + { name: 'player', internalType: 'address', type: 'address' }, + ], + name: 'getPublicRandomness', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [{ name: '', internalType: 'address', type: 'address' }], + name: 'inGame', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'inventory', + outputs: [ + { name: '', internalType: 'contract Inventory', type: 'address' }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'gameID', internalType: 'uint256', type: 'uint256' }, + { name: 'deckID', internalType: 'uint8', type: 'uint8' }, + { name: 'saltHash', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + name: 'joinGame', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'gameID', internalType: 'uint256', type: 'uint256' }, + { name: 'handRoot', internalType: 'bytes32', type: 'bytes32' }, + { name: 'cardIndexInHand', internalType: 'uint8', type: 'uint8' }, + { name: 'cardIndex', internalType: 'uint8', type: 'uint8' }, + { name: 'proofA', internalType: 'uint256[2]', type: 'uint256[2]' }, + { name: 'proofB', internalType: 'uint256[2][2]', type: 'uint256[2][2]' }, + { name: 'proofC', internalType: 'uint256[2]', type: 'uint256[2]' }, + ], + name: 'playCard', + outputs: [], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'playVerifier', + outputs: [ + { name: '', internalType: 'contract Groth16Verifier', type: 'address' }, + ], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [{ name: 'player', internalType: 'address', type: 'address' }], + name: 'playerActive', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [ + { name: 'gameID', internalType: 'uint256', type: 'uint256' }, + { name: 'player', internalType: 'address', type: 'address' }, + ], + name: 'playerData', + outputs: [ + { + name: '', + internalType: 'struct PlayerData', + type: 'tuple', + components: [ + { name: 'health', internalType: 'uint16', type: 'uint16' }, + { name: 'defeated', internalType: 'bool', type: 'bool' }, + { name: 'deckStart', internalType: 'uint8', type: 'uint8' }, + { name: 'deckEnd', internalType: 'uint8', type: 'uint8' }, + { name: 'handSize', internalType: 'uint8', type: 'uint8' }, + { name: 'deckSize', internalType: 'uint8', type: 'uint8' }, + { name: 'joinBlockNum', internalType: 'uint256', type: 'uint256' }, + { name: 'saltHash', internalType: 'uint256', type: 'uint256' }, + { name: 'handRoot', internalType: 'bytes32', type: 'bytes32' }, + { name: 'deckRoot', internalType: 'bytes32', type: 'bytes32' }, + { name: 'battlefield', internalType: 'uint256', type: 'uint256' }, + { name: 'graveyard', internalType: 'uint256', type: 'uint256' }, + { name: 'attacking', internalType: 'uint8[]', type: 'uint8[]' }, + ], + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [ + { name: 'gameID', internalType: 'uint256', type: 'uint256' }, + { name: 'player', internalType: 'address', type: 'address' }, + ], + name: 'playerDeck', + outputs: [{ name: '', internalType: 'uint256[]', type: 'uint256[]' }], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [{ name: 'gameID', internalType: 'uint256', type: 'uint256' }], + name: 'timeout', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [], + name: 'toggleCheckProofs', + outputs: [], + }, + ] as const + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Inventory + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + export const inventoryABI = [ + { + stateMutability: 'nonpayable', + type: 'constructor', + inputs: [ + { name: 'deploySalt', internalType: 'bytes32', type: 'bytes32' }, + { + name: 'cardsCollection_', + internalType: 'contract CardsCollection', + type: 'address', + }, + ], + }, + { type: 'error', inputs: [], name: 'BigDeckEnergy' }, + { + type: 'error', + inputs: [{ name: 'cardID', internalType: 'uint256', type: 'uint256' }], + name: 'CardExceedsMaxCopy', + }, + { + type: 'error', + inputs: [{ name: 'cardID', internalType: 'uint256', type: 'uint256' }], + name: 'CardNotInInventory', + }, + { + type: 'error', + inputs: [ + { name: 'player', internalType: 'address', type: 'address' }, + { name: 'deckID', internalType: 'uint8', type: 'uint8' }, + ], + name: 'DeckDoesNotExist', + }, + { type: 'error', inputs: [], name: 'OutOfDeckIDs' }, + { + type: 'error', + inputs: [{ name: 'player', internalType: 'address', type: 'address' }], + name: 'PlayerIsInActiveGame', + }, + { type: 'error', inputs: [], name: 'PlayerNotDelegatedToSender' }, + { type: 'error', inputs: [], name: 'SmallDeckEnergy' }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'player', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'cardID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + ], + name: 'CardAdded', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'deckID', internalType: 'uint8', type: 'uint8', indexed: true }, + { + name: 'cardID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + ], + name: 'CardAddedToDeck', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'player', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'cardID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + ], + name: 'CardRemoved', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'deckID', internalType: 'uint8', type: 'uint8', indexed: true }, + { + name: 'cardID', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + ], + name: 'CardRemovedFromDeck', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'player', + internalType: 'address', + type: 'address', + indexed: true, + }, + { name: 'deckID', internalType: 'uint8', type: 'uint8', indexed: false }, + ], + name: 'DeckAdded', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'player', + internalType: 'address', + type: 'address', + indexed: true, + }, + { name: 'deckID', internalType: 'uint8', type: 'uint8', indexed: true }, + ], + name: 'DeckRemoved', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'previousOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'newOwner', + internalType: 'address', + type: 'address', + indexed: true, + }, + ], + name: 'OwnershipTransferred', + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'MAX_DECKS', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'MAX_DECK_SIZE', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'MIN_DECK_SIZE', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'player', internalType: 'address', type: 'address' }, + { name: 'cardID', internalType: 'uint256', type: 'uint256' }, + ], + name: 'addCard', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'player', internalType: 'address', type: 'address' }, + { name: 'deckID', internalType: 'uint8', type: 'uint8' }, + { name: 'cardID', internalType: 'uint256', type: 'uint256' }, + ], + name: 'addCardToDeck', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'player', internalType: 'address', type: 'address' }, + { + name: 'deck', + internalType: 'struct Inventory.Deck', + type: 'tuple', + components: [ + { name: 'cards', internalType: 'uint256[]', type: 'uint256[]' }, + ], + }, + ], + name: 'addDeck', + outputs: [{ name: 'deckID', internalType: 'uint8', type: 'uint8' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'airdrop', + outputs: [{ name: '', internalType: 'address', type: 'address' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [ + { name: 'player', internalType: 'address', type: 'address' }, + { name: 'deckID', internalType: 'uint8', type: 'uint8' }, + ], + name: 'checkDeck', + outputs: [], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + name: 'delegations', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'game', + outputs: [{ name: '', internalType: 'contract Game', type: 'address' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [ + { name: 'cardIDArr', internalType: 'uint256[]', type: 'uint256[]' }, + ], + name: 'getCardTypes', + outputs: [{ name: '', internalType: 'uint256[]', type: 'uint256[]' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [ + { name: 'player', internalType: 'address', type: 'address' }, + { name: 'deckID', internalType: 'uint8', type: 'uint8' }, + ], + name: 'getDeck', + outputs: [ + { name: 'deckCards', internalType: 'uint256[]', type: 'uint256[]' }, + ], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [{ name: 'player', internalType: 'address', type: 'address' }], + name: 'getNumDecks', + outputs: [{ name: '', internalType: 'uint8', type: 'uint8' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'inventoryCardsCollection', + outputs: [ + { + name: '', + internalType: 'contract InventoryCardsCollection', + type: 'address', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'originalCardsCollection', + outputs: [ + { name: '', internalType: 'contract CardsCollection', type: 'address' }, + ], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'owner', + outputs: [{ name: '', internalType: 'address', type: 'address' }], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'player', internalType: 'address', type: 'address' }, + { name: 'cardID', internalType: 'uint256', type: 'uint256' }, + ], + name: 'removeCard', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'player', internalType: 'address', type: 'address' }, + { name: 'deckID', internalType: 'uint8', type: 'uint8' }, + { name: 'index', internalType: 'uint8', type: 'uint8' }, + ], + name: 'removeCardFromDeck', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'player', internalType: 'address', type: 'address' }, + { name: 'deckID', internalType: 'uint8', type: 'uint8' }, + ], + name: 'removeDeck', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [], + name: 'renounceOwnership', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'player', internalType: 'address', type: 'address' }, + { name: 'deckID', internalType: 'uint8', type: 'uint8' }, + { + name: 'deck', + internalType: 'struct Inventory.Deck', + type: 'tuple', + components: [ + { name: 'cards', internalType: 'uint256[]', type: 'uint256[]' }, + ], + }, + ], + name: 'replaceDeck', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { + name: 'airdrop_', + internalType: 'contract DeckAirdrop', + type: 'address', + }, + ], + name: 'setAirdrop', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'delegate', internalType: 'address', type: 'address' }, + { name: 'isDelegated', internalType: 'bool', type: 'bool' }, + ], + name: 'setDelegation', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [{ name: 'game_', internalType: 'contract Game', type: 'address' }], + name: 'setGame', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [{ name: 'newOwner', internalType: 'address', type: 'address' }], + name: 'transferOwnership', + outputs: [], + }, + ] as const + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // InventoryCardsCollection + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + export const inventoryCardsCollectionABI = [ + { + stateMutability: 'nonpayable', + type: 'constructor', + inputs: [ + { + name: 'cardsCollection_', + internalType: 'contract CardsCollection', + type: 'address', + }, + ], + }, + { type: 'error', inputs: [], name: 'CallerNotInventory' }, + { + type: 'error', + inputs: [{ name: 'cardID', internalType: 'uint256', type: 'uint256' }], + name: 'CardNotInInventory', + }, + { type: 'error', inputs: [], name: 'TokenIsSoulbound' }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'owner', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'approved', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'tokenId', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + ], + name: 'Approval', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'owner', + internalType: 'address', + type: 'address', + indexed: true, + }, + { + name: 'operator', + internalType: 'address', + type: 'address', + indexed: true, + }, + { name: 'approved', internalType: 'bool', type: 'bool', indexed: false }, + ], + name: 'ApprovalForAll', + }, + { + type: 'event', + anonymous: false, + inputs: [ + { name: 'from', internalType: 'address', type: 'address', indexed: true }, + { name: 'to', internalType: 'address', type: 'address', indexed: true }, + { + name: 'tokenId', + internalType: 'uint256', + type: 'uint256', + indexed: true, + }, + ], + name: 'Transfer', + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'tokenId', internalType: 'uint256', type: 'uint256' }, + ], + name: 'approve', + outputs: [], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [{ name: 'owner', internalType: 'address', type: 'address' }], + name: 'balanceOf', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [{ name: 'tokenID', internalType: 'uint256', type: 'uint256' }], + name: 'burn', + outputs: [], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'cardsCollection', + outputs: [ + { name: '', internalType: 'contract CardsCollection', type: 'address' }, + ], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [{ name: 'tokenId', internalType: 'uint256', type: 'uint256' }], + name: 'getApproved', + outputs: [{ name: '', internalType: 'address', type: 'address' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [{ name: 'player', internalType: 'address', type: 'address' }], + name: 'getCollection', + outputs: [ + { + name: 'collectionCards', + internalType: 'struct Card[]', + type: 'tuple[]', + components: [ + { name: 'id', internalType: 'uint256', type: 'uint256' }, + { + name: 'lore', + internalType: 'struct Lore', + type: 'tuple', + components: [ + { name: 'name', internalType: 'string', type: 'string' }, + { name: 'flavor', internalType: 'string', type: 'string' }, + { name: 'URL', internalType: 'string', type: 'string' }, + ], + }, + { + name: 'stats', + internalType: 'struct Stats', + type: 'tuple', + components: [ + { name: 'attack', internalType: 'uint8', type: 'uint8' }, + { name: 'defense', internalType: 'uint8', type: 'uint8' }, + ], + }, + { name: 'cardTypeID', internalType: 'uint32', type: 'uint32' }, + ], + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'inventory', + outputs: [{ name: '', internalType: 'address', type: 'address' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [ + { name: 'owner', internalType: 'address', type: 'address' }, + { name: 'operator', internalType: 'address', type: 'address' }, + ], + name: 'isApprovedForAll', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'tokenID', internalType: 'uint256', type: 'uint256' }, + ], + name: 'mint', + outputs: [], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'name', + outputs: [{ name: '', internalType: 'string', type: 'string' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [{ name: 'tokenId', internalType: 'uint256', type: 'uint256' }], + name: 'ownerOf', + outputs: [{ name: '', internalType: 'address', type: 'address' }], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'from', internalType: 'address', type: 'address' }, + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'tokenId', internalType: 'uint256', type: 'uint256' }, + ], + name: 'safeTransferFrom', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'from', internalType: 'address', type: 'address' }, + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'tokenId', internalType: 'uint256', type: 'uint256' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + name: 'safeTransferFrom', + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'operator', internalType: 'address', type: 'address' }, + { name: 'approved', internalType: 'bool', type: 'bool' }, + ], + name: 'setApprovalForAll', + outputs: [], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [{ name: 'interfaceId', internalType: 'bytes4', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'symbol', + outputs: [{ name: '', internalType: 'string', type: 'string' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [{ name: 'index', internalType: 'uint256', type: 'uint256' }], + name: 'tokenByIndex', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [ + { name: 'owner', internalType: 'address', type: 'address' }, + { name: 'index', internalType: 'uint256', type: 'uint256' }, + ], + name: 'tokenOfOwnerByIndex', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [{ name: 'tokenId', internalType: 'uint256', type: 'uint256' }], + name: 'tokenURI', + outputs: [{ name: '', internalType: 'string', type: 'string' }], + }, + { + stateMutability: 'view', + type: 'function', + inputs: [], + name: 'totalSupply', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + }, + { + stateMutability: 'nonpayable', + type: 'function', + inputs: [ + { name: 'from', internalType: 'address', type: 'address' }, + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'tokenId', internalType: 'uint256', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [], + }, + ] as const + \ No newline at end of file diff --git a/packages/webapp/src/utils/viem-learning.int.test.ts b/packages/webapp/src/utils/viem-learning.int.test.ts new file mode 100644 index 00000000..1bcbe57a --- /dev/null +++ b/packages/webapp/src/utils/viem-learning.int.test.ts @@ -0,0 +1,124 @@ +/* +* Requires running rpc +*/ + +import { describe, beforeAll, it, jest, expect } from '@jest/globals'; + +// do not import generated as to avoid wagmi dependency for now until we setup jest transform +import { gameABI } from "./generated-abi" +import { privateKeyToAccount } from 'viem/accounts'; +import * as deployment_ from "../../../contracts/out/deployment.json" +import { sendRawTransaction } from 'viem/actions'; +import { Address,createTestClient, encodeFunctionData, http, publicActions, walletActions } from 'viem' + +import { localhost } from 'viem/chains' + +const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' + +const account = privateKeyToAccount(TEST_PRIVATE_KEY) + + +const client = createTestClient({ + chain: localhost, + mode: 'anvil', + transport: http(), + }) + .extend(publicActions) + .extend(walletActions) + +jest.setTimeout(1000000) +describe("viem", () => { + + let gameIds: bigint[] = []; + + const createGame = async ()=>{ + + console.log('create game') + const createGameData = encodeFunctionData({ + abi: gameABI, + functionName: 'createGame', + args: [2], + }); + + const result = await client.sendTransaction({ + to: deployment_.Game as Address, + data: createGameData, + account, + }); + + const receipt = await client.waitForTransactionReceipt({hash: result}) + console.log(receipt) + + + gameIds.push(BigInt(receipt.logs[0].data)); + + } + const joinGame = async (gameId: bigint)=>{ + console.log('join game', gameId) + const joinGameData = encodeFunctionData({ + abi: gameABI, + functionName: 'joinGame', + args: [gameId, 0, 1n, '0x00'], + }); + + const result = await client.sendTransaction({ + to: deployment_.Game as Address, + data: joinGameData, + account, + }); + + const receipt = await client.waitForTransactionReceipt({hash: result}) + console.log('join game completed') + console.log(receipt) + } + + + beforeAll(async () => { + await createGame() + await createGame() + }); + + it("Should listen to event", (done) => { + + const eventNames = [ + + "PlayerJoined", + + ] + + const eventsABI = gameABI.filter((abi) => abi.type === "event" && eventNames.includes(abi.name)); + + const onLogs = jest.fn().mockImplementation((logs)=>{ + console.log('logs') + console.log(logs) + }) + + + console.log('watch') + + client.watchEvent({ + address: deployment_.Game as Address, + events: eventsABI, + // @ts-ignore + args: { gameID: gameIds[0] }, + strict: true, + onLogs + }) + + setTimeout(async ()=>{ + + await joinGame(gameIds[0]) + await joinGame(gameIds[1]) + + }, 3000) + + + setTimeout(()=>{ + console.log('done') + expect(onLogs).toHaveBeenCalledTimes(2) + expect(onLogs).toHaveBeenCalledWith(1) + done() + }, 20000) + }) + +}) \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40703447..0ffcf51b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,7 +28,7 @@ importers: devDependencies: jest: specifier: ^29.7.0 - version: 29.7.0 + version: 29.7.0(@types/node@20.8.7)(ts-node@10.9.2) packages/contracts: dependencies: @@ -139,6 +139,9 @@ importers: specifier: ^1.4.5 version: 1.4.5(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.16.6) devDependencies: + '@jest/globals': + specifier: ^29.7.0 + version: 29.7.0 '@swc-jotai/debug-label': specifier: ^0.1.0 version: 0.1.0 @@ -181,12 +184,21 @@ importers: eslint-config-next: specifier: ^13.5.6 version: 13.5.6(eslint@8.52.0)(typescript@5.2.2) + jest: + specifier: 29.7.0 + version: 29.7.0(@types/node@20.8.7)(ts-node@10.9.2) postcss: specifier: ^8.4.31 version: 8.4.31 tailwindcss: specifier: ^3.3.3 - version: 3.3.3 + version: 3.3.3(ts-node@10.9.2) + ts-jest: + specifier: 29.1.2 + version: 29.1.2(@babel/core@7.23.2)(esbuild@0.16.17)(jest@29.7.0)(typescript@5.2.2) + ts-node: + specifier: 10.9.2 + version: 10.9.2(@types/node@20.8.7)(typescript@5.2.2) typescript: specifier: ^5.2.2 version: 5.2.2 @@ -323,7 +335,7 @@ packages: '@babel/core': 7.23.2 '@babel/helper-compilation-targets': 7.22.15 '@babel/helper-plugin-utils': 7.22.5 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -1431,6 +1443,12 @@ packages: dev: true optional: true + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + /@cypress/code-coverage@3.12.6(@babel/core@7.23.2)(@babel/preset-env@7.23.2)(babel-loader@9.1.3)(cypress@12.17.4)(webpack@5.89.0): resolution: {integrity: sha512-QeOB54dkxee8LKdbK9NqMinlMtzy60K2DexSvx4XZdrU52gut9Ukc7sUvRe44gK1yJFYno977Kx9miNIeYWVtA==} peerDependencies: @@ -1447,7 +1465,7 @@ packages: chalk: 4.1.2 cypress: 12.17.4 dayjs: 1.11.10 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) execa: 4.1.0 globby: 11.0.4 istanbul-lib-coverage: 3.0.0 @@ -1516,7 +1534,7 @@ packages: '@babel/preset-env': 7.23.2(@babel/core@7.23.2) babel-loader: 9.1.3(@babel/core@7.23.2)(webpack@5.89.0) bluebird: 3.7.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) lodash: 4.17.21 webpack: 5.89.0 transitivePeerDependencies: @@ -1628,6 +1646,7 @@ packages: /@emotion/memoize@0.7.4: resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==} + requiresBuild: true dev: false optional: true @@ -2365,7 +2384,7 @@ packages: slash: 3.0.0 dev: true - /@jest/core@29.7.0: + /@jest/core@29.7.0(ts-node@10.9.2): resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -2386,7 +2405,7 @@ packages: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.8.7) + jest-config: 29.7.0(@types/node@20.8.7)(ts-node@10.9.2) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -2610,6 +2629,12 @@ packages: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + /@ledgerhq/connect-kit-loader@1.1.2: resolution: {integrity: sha512-mscwGroSJQrCTjtNGBu+18FQbZYA4+q6Tyx6K7CXHl6AwgZKbWfZYdgP2F+fyZcRUdGRsMX8QtvU61VcGGtO1A==} @@ -3700,7 +3725,7 @@ packages: commander: 10.0.1 cypress: 12.17.4 cypress-wait-until: 1.7.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) dotenv: 16.3.1 dotenv-parse-variables: 2.0.0 download: 8.0.0 @@ -3833,6 +3858,18 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@tsconfig/node10@1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + /@types/aria-query@4.2.2: resolution: {integrity: sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==} dev: true @@ -4984,11 +5021,14 @@ packages: acorn: 8.11.2 dev: true + /acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + /acorn@8.11.2: resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} engines: {node: '>=0.4.0'} hasBin: true - dev: true /aes-js@3.0.0: resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==} @@ -5156,6 +5196,9 @@ packages: resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} dev: true + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + /arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -5814,6 +5857,13 @@ packages: node-releases: 2.0.13 update-browserslist-db: 1.0.13(browserslist@4.22.1) + /bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + dependencies: + fast-json-stable-stringify: 2.1.0 + dev: true + /bs58@4.0.1: resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} dependencies: @@ -6453,7 +6503,7 @@ packages: path-type: 4.0.0 dev: true - /create-jest@29.7.0: + /create-jest@29.7.0(@types/node@20.8.7)(ts-node@10.9.2): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -6462,7 +6512,7 @@ packages: chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.8.7) + jest-config: 29.7.0(@types/node@20.8.7)(ts-node@10.9.2) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -6472,6 +6522,9 @@ packages: - ts-node dev: true + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + /cross-fetch@3.1.8: resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} dependencies: @@ -6618,17 +6671,6 @@ packages: ms: 2.0.0 dev: true - /debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.3 - dev: true - /debug@3.2.7(supports-color@8.1.1): resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -6884,6 +6926,10 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + /dijkstrajs@1.0.3: resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} @@ -6975,7 +7021,7 @@ packages: resolution: {integrity: sha512-/Tezlx6xpDqR6zKg1V4vLCeQtHWiELhWoBz5A/E0+A1lXN9iIkNbbfc4THSymS0LQUo8F1PMiIwVG8ai/HrnSA==} engines: {node: '>= 8.3.0'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) is-string-and-not-blank: 0.0.2 transitivePeerDependencies: - supports-color @@ -7321,7 +7367,7 @@ packages: /eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} dependencies: - debug: 3.2.7 + debug: 3.2.7(supports-color@8.1.1) is-core-module: 2.13.1 resolve: 1.22.8 transitivePeerDependencies: @@ -7373,7 +7419,7 @@ packages: optional: true dependencies: '@typescript-eslint/parser': 6.8.0(eslint@8.52.0)(typescript@5.2.2) - debug: 3.2.7 + debug: 3.2.7(supports-color@8.1.1) eslint: 8.52.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.8.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0)(eslint@8.52.0) @@ -7396,7 +7442,7 @@ packages: array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 - debug: 3.2.7 + debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 eslint: 8.52.0 eslint-import-resolver-node: 0.3.9 @@ -8124,7 +8170,7 @@ packages: debug: optional: true dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) dev: true /for-each@0.3.3: @@ -9347,7 +9393,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) istanbul-lib-coverage: 3.2.0 source-map: 0.6.1 transitivePeerDependencies: @@ -9454,7 +9500,7 @@ packages: - supports-color dev: true - /jest-cli@29.7.0: + /jest-cli@29.7.0(@types/node@20.8.7)(ts-node@10.9.2): resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -9464,14 +9510,14 @@ packages: node-notifier: optional: true dependencies: - '@jest/core': 29.7.0 + '@jest/core': 29.7.0(ts-node@10.9.2) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0 + create-jest: 29.7.0(@types/node@20.8.7)(ts-node@10.9.2) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.8.7) + jest-config: 29.7.0(@types/node@20.8.7)(ts-node@10.9.2) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -9482,7 +9528,7 @@ packages: - ts-node dev: true - /jest-config@29.7.0(@types/node@20.8.7): + /jest-config@29.7.0(@types/node@20.8.7)(ts-node@10.9.2): resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -9517,6 +9563,7 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 + ts-node: 10.9.2(@types/node@20.8.7)(typescript@5.2.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -9814,7 +9861,7 @@ packages: supports-color: 8.1.1 dev: true - /jest@29.7.0: + /jest@29.7.0(@types/node@20.8.7)(ts-node@10.9.2): resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -9824,10 +9871,10 @@ packages: node-notifier: optional: true dependencies: - '@jest/core': 29.7.0 + '@jest/core': 29.7.0(ts-node@10.9.2) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0 + jest-cli: 29.7.0(@types/node@20.8.7)(ts-node@10.9.2) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -10227,6 +10274,10 @@ packages: resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} dev: true + /lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + dev: true + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -10355,6 +10406,9 @@ packages: semver: 7.5.4 dev: true + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + /makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} dependencies: @@ -11206,7 +11260,7 @@ packages: camelcase-css: 2.0.1 postcss: 8.4.31 - /postcss-load-config@4.0.1(postcss@8.4.31): + /postcss-load-config@4.0.1(postcss@8.4.31)(ts-node@10.9.2): resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} engines: {node: '>= 14'} peerDependencies: @@ -11220,6 +11274,7 @@ packages: dependencies: lilconfig: 2.1.0 postcss: 8.4.31 + ts-node: 10.9.2(@types/node@20.8.7)(typescript@5.2.2) yaml: 2.3.3 /postcss-nested@6.0.1(postcss@8.4.31): @@ -12367,7 +12422,7 @@ packages: /spdy-transport@3.0.0: resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) detect-node: 2.1.0 hpack.js: 2.1.6 obuf: 1.1.2 @@ -12381,7 +12436,7 @@ packages: resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} engines: {node: '>=6.0.0'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) handle-thing: 2.0.1 http-deceiver: 1.2.7 select-hose: 2.0.0 @@ -12735,10 +12790,10 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders' dependencies: - tailwindcss: 3.3.3 + tailwindcss: 3.3.3(ts-node@10.9.2) dev: false - /tailwindcss@3.3.3: + /tailwindcss@3.3.3(ts-node@10.9.2): resolution: {integrity: sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==} engines: {node: '>=14.0.0'} hasBin: true @@ -12760,7 +12815,7 @@ packages: postcss: 8.4.31 postcss-import: 15.1.0(postcss@8.4.31) postcss-js: 4.0.1(postcss@8.4.31) - postcss-load-config: 4.0.1(postcss@8.4.31) + postcss-load-config: 4.0.1(postcss@8.4.31)(ts-node@10.9.2) postcss-nested: 6.0.1(postcss@8.4.31) postcss-selector-parser: 6.0.13 resolve: 1.22.8 @@ -12958,6 +13013,71 @@ packages: /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + /ts-jest@29.1.2(@babel/core@7.23.2)(esbuild@0.16.17)(jest@29.7.0)(typescript@5.2.2): + resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} + engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.23.2 + bs-logger: 0.2.6 + esbuild: 0.16.17 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@20.8.7)(ts-node@10.9.2) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.5.4 + typescript: 5.2.2 + yargs-parser: 21.1.1 + dev: true + + /ts-node@10.9.2(@types/node@20.8.7)(typescript@5.2.2): + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.8.7 + acorn: 8.11.2 + acorn-walk: 8.3.2 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.2.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + /tsconfig-paths@3.14.2: resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} dependencies: @@ -13323,6 +13443,9 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + /v8-to-istanbul@9.1.3: resolution: {integrity: sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==} engines: {node: '>=10.12.0'} @@ -13893,6 +14016,10 @@ packages: fd-slicer: 1.1.0 dev: true + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'}