diff --git a/package.json b/package.json index f0c5a2a..a7d354a 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ }, "devDependencies": { "@biomejs/biome": "1.9.4", + "@types/fs-extra": "^11.0.4", "@types/js-yaml": "^4.0.9", "@types/node": "^22.13.1", "@vitest/coverage-v8": "3.0.5", @@ -60,6 +61,7 @@ "ethers": "^6.13.5", "fastify": "^5.2.1", "fastify-plugin": "^5.0.1", + "fs-extra": "^11.3.2", "js-yaml": "^4.1.0", "pino": "^9.7.0", "prisma": "^6.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c94e16a..bb9f820 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,6 +92,9 @@ importers: fastify-plugin: specifier: ^5.0.1 version: 5.0.1 + fs-extra: + specifier: ^11.3.2 + version: 11.3.2 js-yaml: specifier: ^4.1.0 version: 4.1.0 @@ -117,6 +120,9 @@ importers: '@biomejs/biome': specifier: 1.9.4 version: 1.9.4 + '@types/fs-extra': + specifier: ^11.0.4 + version: 11.0.4 '@types/js-yaml': specifier: ^4.0.9 version: 4.0.9 @@ -1213,9 +1219,15 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/fs-extra@11.0.4': + resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/jsonfile@6.1.4': + resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + '@types/long@4.0.2': resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} @@ -1677,6 +1689,10 @@ packages: resolution: {integrity: sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==} engines: {node: '>= 0.12'} + fs-extra@11.3.2: + resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} + engines: {node: '>=14.14'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1741,6 +1757,9 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + gtoken@7.1.0: resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} engines: {node: '>=14.0.0'} @@ -1881,6 +1900,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jwa@2.0.1: resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} @@ -2373,6 +2395,10 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -3985,8 +4011,17 @@ snapshots: '@types/estree@1.0.8': {} + '@types/fs-extra@11.0.4': + dependencies: + '@types/jsonfile': 6.1.4 + '@types/node': 22.18.1 + '@types/js-yaml@4.0.9': {} + '@types/jsonfile@6.1.4': + dependencies: + '@types/node': 22.18.1 + '@types/long@4.0.2': {} '@types/node@22.18.1': @@ -4487,6 +4522,12 @@ snapshots: mime-types: 2.1.35 safe-buffer: 5.2.1 + fs-extra@11.3.2: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + fsevents@2.3.3: optional: true @@ -4599,6 +4640,8 @@ snapshots: gopd@1.2.0: {} + graceful-fs@4.2.11: {} + gtoken@7.1.0: dependencies: gaxios: 6.7.1 @@ -4763,6 +4806,12 @@ snapshots: json-schema-traverse@1.0.0: {} + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + jwa@2.0.1: dependencies: buffer-equal-constant-time: 1.0.1 @@ -5271,6 +5320,8 @@ snapshots: undici-types@6.21.0: {} + universalify@2.0.1: {} + util-deprecate@1.0.2: {} uuid@8.3.2: {} diff --git a/src/constants/abis/erc1155Items.ts b/src/constants/abis/erc1155Items.ts index 5d393c2..e9a21ae 100644 --- a/src/constants/abis/erc1155Items.ts +++ b/src/constants/abis/erc1155Items.ts @@ -1,524 +1 @@ -export const erc1155ItemsAbi = [ - { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, - { inputs: [], name: 'AccountBalanceOverflow', type: 'error' }, - { inputs: [], name: 'ArrayLengthsMismatch', type: 'error' }, - { inputs: [], name: 'InsufficientBalance', type: 'error' }, - { inputs: [], name: 'InvalidArrayLength', type: 'error' }, - { inputs: [], name: 'InvalidInitialization', type: 'error' }, - { inputs: [], name: 'NotOwnerNorApproved', type: 'error' }, - { - inputs: [], - name: 'TransferToNonERC1155ReceiverImplementer', - type: 'error' - }, - { inputs: [], name: 'TransferToZeroAddress', type: 'error' }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address' - }, - { - indexed: true, - internalType: 'address', - name: 'operator', - type: 'address' - }, - { indexed: false, internalType: 'bool', name: 'isApproved', type: 'bool' } - ], - name: 'ApprovalForAll', - type: 'event' - }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'bytes32', name: 'role', type: 'bytes32' }, - { - indexed: true, - internalType: 'bytes32', - name: 'previousAdminRole', - type: 'bytes32' - }, - { - indexed: true, - internalType: 'bytes32', - name: 'newAdminRole', - type: 'bytes32' - } - ], - name: 'RoleAdminChanged', - type: 'event' - }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'bytes32', name: 'role', type: 'bytes32' }, - { - indexed: true, - internalType: 'address', - name: 'account', - type: 'address' - }, - { - indexed: true, - internalType: 'address', - name: 'sender', - type: 'address' - } - ], - name: 'RoleGranted', - type: 'event' - }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: 'bytes32', name: 'role', type: 'bytes32' }, - { - indexed: true, - internalType: 'address', - name: 'account', - type: 'address' - }, - { - indexed: true, - internalType: 'address', - name: 'sender', - type: 'address' - } - ], - name: 'RoleRevoked', - type: 'event' - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'operator', - type: 'address' - }, - { indexed: true, internalType: 'address', name: 'from', type: 'address' }, - { indexed: true, internalType: 'address', name: 'to', type: 'address' }, - { - indexed: false, - internalType: 'uint256[]', - name: 'ids', - type: 'uint256[]' - }, - { - indexed: false, - internalType: 'uint256[]', - name: 'amounts', - type: 'uint256[]' - } - ], - name: 'TransferBatch', - type: 'event' - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'operator', - type: 'address' - }, - { indexed: true, internalType: 'address', name: 'from', type: 'address' }, - { indexed: true, internalType: 'address', name: 'to', type: 'address' }, - { indexed: false, internalType: 'uint256', name: 'id', type: 'uint256' }, - { - indexed: false, - internalType: 'uint256', - name: 'amount', - type: 'uint256' - } - ], - name: 'TransferSingle', - type: 'event' - }, - { - anonymous: false, - inputs: [ - { indexed: false, internalType: 'string', name: 'value', type: 'string' }, - { indexed: true, internalType: 'uint256', name: 'id', type: 'uint256' } - ], - name: 'URI', - type: 'event' - }, - { - inputs: [], - name: 'DEFAULT_ADMIN_ROLE', - outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { internalType: 'address', name: 'wallet', type: 'address' }, - { - components: [ - { internalType: 'address', name: 'approvedSigner', type: 'address' }, - { internalType: 'bytes4', name: 'identityType', type: 'bytes4' }, - { internalType: 'bytes32', name: 'issuerHash', type: 'bytes32' }, - { internalType: 'bytes32', name: 'audienceHash', type: 'bytes32' }, - { internalType: 'bytes', name: 'applicationData', type: 'bytes' }, - { - components: [ - { internalType: 'string', name: 'redirectUrl', type: 'string' }, - { internalType: 'uint64', name: 'issuedAt', type: 'uint64' } - ], - internalType: 'struct AuthData', - name: 'authData', - type: 'tuple' - } - ], - internalType: 'struct Attestation', - name: 'attestation', - type: 'tuple' - }, - { - components: [ - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'value', type: 'uint256' }, - { internalType: 'bytes', name: 'data', type: 'bytes' }, - { internalType: 'uint256', name: 'gasLimit', type: 'uint256' }, - { internalType: 'bool', name: 'delegateCall', type: 'bool' }, - { internalType: 'bool', name: 'onlyFallback', type: 'bool' }, - { internalType: 'uint256', name: 'behaviorOnError', type: 'uint256' } - ], - internalType: 'struct Payload.Call', - name: 'call', - type: 'tuple' - } - ], - name: 'acceptImplicitRequest', - outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { internalType: 'address', name: 'owner', type: 'address' }, - { internalType: 'uint256', name: 'id', type: 'uint256' } - ], - name: 'balanceOf', - outputs: [{ internalType: 'uint256', name: 'result', type: 'uint256' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { internalType: 'address[]', name: 'owners', type: 'address[]' }, - { internalType: 'uint256[]', name: 'ids', type: 'uint256[]' } - ], - name: 'balanceOfBatch', - outputs: [ - { internalType: 'uint256[]', name: 'balances', type: 'uint256[]' } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'baseURI', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { internalType: 'uint256[]', name: 'tokenIds', type: 'uint256[]' }, - { internalType: 'uint256[]', name: 'amounts', type: 'uint256[]' } - ], - name: 'batchBurn', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256[]', name: 'tokenIds', type: 'uint256[]' }, - { internalType: 'uint256[]', name: 'amounts', type: 'uint256[]' }, - { internalType: 'bytes', name: 'data', type: 'bytes' } - ], - name: 'batchMint', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'uint256', name: 'amount', type: 'uint256' } - ], - name: 'burn', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [], - name: 'contractURI', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [{ internalType: 'bytes32', name: 'role', type: 'bytes32' }], - name: 'getRoleAdmin', - outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { internalType: 'bytes32', name: 'role', type: 'bytes32' }, - { internalType: 'uint256', name: 'index', type: 'uint256' } - ], - name: 'getRoleMember', - outputs: [{ internalType: 'address', name: '', type: 'address' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [{ internalType: 'bytes32', name: 'role', type: 'bytes32' }], - name: 'getRoleMemberCount', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { internalType: 'bytes32', name: 'role', type: 'bytes32' }, - { internalType: 'address', name: 'account', type: 'address' } - ], - name: 'grantRole', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { internalType: 'bytes32', name: 'role', type: 'bytes32' }, - { internalType: 'address', name: 'account', type: 'address' } - ], - name: 'hasRole', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { internalType: 'address', name: 'owner', type: 'address' }, - { internalType: 'string', name: 'tokenName', type: 'string' }, - { internalType: 'string', name: 'tokenBaseURI', type: 'string' }, - { internalType: 'string', name: 'tokenContractURI', type: 'string' }, - { internalType: 'address', name: 'royaltyReceiver', type: 'address' }, - { internalType: 'uint96', name: 'royaltyFeeNumerator', type: 'uint96' }, - { - internalType: 'address', - name: 'implicitModeValidator', - type: 'address' - }, - { - internalType: 'bytes32', - name: 'implicitModeProjectId', - type: 'bytes32' - } - ], - name: 'initialize', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { internalType: 'address', name: 'owner', type: 'address' }, - { internalType: 'address', name: 'operator', type: 'address' } - ], - name: 'isApprovedForAll', - outputs: [{ internalType: 'bool', name: 'result', type: 'bool' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'uint256', name: 'amount', type: 'uint256' }, - { internalType: 'bytes', name: 'data', type: 'bytes' } - ], - name: 'mint', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [], - name: 'name', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { internalType: 'bytes32', name: 'role', type: 'bytes32' }, - { internalType: 'address', name: 'account', type: 'address' } - ], - name: 'renounceRole', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { internalType: 'bytes32', name: 'role', type: 'bytes32' }, - { internalType: 'address', name: 'account', type: 'address' } - ], - name: 'revokeRole', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'uint256', name: 'salePrice', type: 'uint256' } - ], - name: 'royaltyInfo', - outputs: [ - { internalType: 'address', name: '', type: 'address' }, - { internalType: 'uint256', name: '', type: 'uint256' } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256[]', name: 'ids', type: 'uint256[]' }, - { internalType: 'uint256[]', name: 'amounts', type: 'uint256[]' }, - { internalType: 'bytes', name: 'data', type: 'bytes' } - ], - name: 'safeBatchTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'id', type: 'uint256' }, - { internalType: 'uint256', name: 'amount', type: 'uint256' }, - { internalType: 'bytes', name: 'data', type: 'bytes' } - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { internalType: 'address', name: 'operator', type: 'address' }, - { internalType: 'bool', name: 'isApproved', type: 'bool' } - ], - name: 'setApprovalForAll', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [{ internalType: 'string', name: 'tokenBaseURI', type: 'string' }], - name: 'setBaseMetadataURI', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [{ internalType: 'string', name: 'tokenName', type: 'string' }], - name: 'setContractName', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { internalType: 'string', name: 'tokenContractURI', type: 'string' } - ], - name: 'setContractURI', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { internalType: 'address', name: 'receiver', type: 'address' }, - { internalType: 'uint96', name: 'feeNumerator', type: 'uint96' } - ], - name: 'setDefaultRoyalty', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [{ internalType: 'bytes32', name: 'projectId', type: 'bytes32' }], - name: 'setImplicitModeProjectId', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [{ internalType: 'address', name: 'validator', type: 'address' }], - name: 'setImplicitModeValidator', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, - { internalType: 'address', name: 'receiver', type: 'address' }, - { internalType: 'uint96', name: 'feeNumerator', type: 'uint96' } - ], - name: 'setTokenRoyalty', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], - name: 'supportsInterface', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - name: 'tokenSupply', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'totalSupply', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [{ internalType: 'uint256', name: '_id', type: 'uint256' }], - name: 'uri', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function' - } -] +export const erc1155ItemsAbi = [{ "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [], "name": "AccountBalanceOverflow", "type": "error" }, { "inputs": [], "name": "ArrayLengthsMismatch", "type": "error" }, { "inputs": [], "name": "InsufficientBalance", "type": "error" }, { "inputs": [], "name": "InvalidArrayLength", "type": "error" }, { "inputs": [], "name": "InvalidInitialization", "type": "error" }, { "inputs": [], "name": "NotOwnerNorApproved", "type": "error" }, { "inputs": [], "name": "TransferToNonERC1155ReceiverImplementer", "type": "error" }, { "inputs": [], "name": "TransferToZeroAddress", "type": "error" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, { "indexed": true, "internalType": "address", "name": "operator", "type": "address" }, { "indexed": false, "internalType": "bool", "name": "isApproved", "type": "bool" }], "name": "ApprovalForAll", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "indexed": true, "internalType": "bytes32", "name": "previousAdminRole", "type": "bytes32" }, { "indexed": true, "internalType": "bytes32", "name": "newAdminRole", "type": "bytes32" }], "name": "RoleAdminChanged", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "indexed": true, "internalType": "address", "name": "account", "type": "address" }, { "indexed": true, "internalType": "address", "name": "sender", "type": "address" }], "name": "RoleGranted", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "indexed": true, "internalType": "address", "name": "account", "type": "address" }, { "indexed": true, "internalType": "address", "name": "sender", "type": "address" }], "name": "RoleRevoked", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "operator", "type": "address" }, { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, { "indexed": false, "internalType": "uint256[]", "name": "ids", "type": "uint256[]" }, { "indexed": false, "internalType": "uint256[]", "name": "amounts", "type": "uint256[]" }], "name": "TransferBatch", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "operator", "type": "address" }, { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "id", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }], "name": "TransferSingle", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": false, "internalType": "string", "name": "value", "type": "string" }, { "indexed": true, "internalType": "uint256", "name": "id", "type": "uint256" }], "name": "URI", "type": "event" }, { "inputs": [], "name": "DEFAULT_ADMIN_ROLE", "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "wallet", "type": "address" }, { "components": [{ "internalType": "address", "name": "approvedSigner", "type": "address" }, { "internalType": "bytes4", "name": "identityType", "type": "bytes4" }, { "internalType": "bytes32", "name": "issuerHash", "type": "bytes32" }, { "internalType": "bytes32", "name": "audienceHash", "type": "bytes32" }, { "internalType": "bytes", "name": "applicationData", "type": "bytes" }, { "components": [{ "internalType": "string", "name": "redirectUrl", "type": "string" }, { "internalType": "uint64", "name": "issuedAt", "type": "uint64" }], "internalType": "struct AuthData", "name": "authData", "type": "tuple" }], "internalType": "struct Attestation", "name": "attestation", "type": "tuple" }, { "components": [{ "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "value", "type": "uint256" }, { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "uint256", "name": "gasLimit", "type": "uint256" }, { "internalType": "bool", "name": "delegateCall", "type": "bool" }, { "internalType": "bool", "name": "onlyFallback", "type": "bool" }, { "internalType": "uint256", "name": "behaviorOnError", "type": "uint256" }], "internalType": "struct Payload.Call", "name": "call", "type": "tuple" }], "name": "acceptImplicitRequest", "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "uint256", "name": "id", "type": "uint256" }], "name": "balanceOf", "outputs": [{ "internalType": "uint256", "name": "result", "type": "uint256" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address[]", "name": "owners", "type": "address[]" }, { "internalType": "uint256[]", "name": "ids", "type": "uint256[]" }], "name": "balanceOfBatch", "outputs": [{ "internalType": "uint256[]", "name": "balances", "type": "uint256[]" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "baseURI", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint256[]", "name": "tokenIds", "type": "uint256[]" }, { "internalType": "uint256[]", "name": "amounts", "type": "uint256[]" }], "name": "batchBurn", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256[]", "name": "tokenIds", "type": "uint256[]" }, { "internalType": "uint256[]", "name": "amounts", "type": "uint256[]" }, { "internalType": "bytes", "name": "data", "type": "bytes" }], "name": "batchMint", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }], "name": "burn", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "contractURI", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }], "name": "getRoleAdmin", "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "uint256", "name": "index", "type": "uint256" }], "name": "getRoleMember", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }], "name": "getRoleMemberCount", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "account", "type": "address" }], "name": "grantRole", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "account", "type": "address" }], "name": "hasRole", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "string", "name": "tokenName", "type": "string" }, { "internalType": "string", "name": "tokenBaseURI", "type": "string" }, { "internalType": "string", "name": "tokenContractURI", "type": "string" }, { "internalType": "address", "name": "royaltyReceiver", "type": "address" }, { "internalType": "uint96", "name": "royaltyFeeNumerator", "type": "uint96" }, { "internalType": "address", "name": "implicitModeValidator", "type": "address" }, { "internalType": "bytes32", "name": "implicitModeProjectId", "type": "bytes32" }], "name": "initialize", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "operator", "type": "address" }], "name": "isApprovedForAll", "outputs": [{ "internalType": "bool", "name": "result", "type": "bool" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "bytes", "name": "data", "type": "bytes" }], "name": "mint", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "name", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "account", "type": "address" }], "name": "renounceRole", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "bytes32", "name": "role", "type": "bytes32" }, { "internalType": "address", "name": "account", "type": "address" }], "name": "revokeRole", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "uint256", "name": "salePrice", "type": "uint256" }], "name": "royaltyInfo", "outputs": [{ "internalType": "address", "name": "", "type": "address" }, { "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "from", "type": "address" }, { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256[]", "name": "ids", "type": "uint256[]" }, { "internalType": "uint256[]", "name": "amounts", "type": "uint256[]" }, { "internalType": "bytes", "name": "data", "type": "bytes" }], "name": "safeBatchTransferFrom", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "from", "type": "address" }, { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "uint256", "name": "id", "type": "uint256" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "bytes", "name": "data", "type": "bytes" }], "name": "safeTransferFrom", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "operator", "type": "address" }, { "internalType": "bool", "name": "isApproved", "type": "bool" }], "name": "setApprovalForAll", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "string", "name": "tokenBaseURI", "type": "string" }], "name": "setBaseMetadataURI", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "string", "name": "tokenName", "type": "string" }], "name": "setContractName", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "string", "name": "tokenContractURI", "type": "string" }], "name": "setContractURI", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "receiver", "type": "address" }, { "internalType": "uint96", "name": "feeNumerator", "type": "uint96" }], "name": "setDefaultRoyalty", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "bytes32", "name": "projectId", "type": "bytes32" }], "name": "setImplicitModeProjectId", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "validator", "type": "address" }], "name": "setImplicitModeValidator", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "address", "name": "receiver", "type": "address" }, { "internalType": "uint96", "name": "feeNumerator", "type": "uint96" }], "name": "setTokenRoyalty", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" }], "name": "supportsInterface", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "name": "tokenSupply", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "totalSupply", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "_id", "type": "uint256" }], "name": "uri", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }] diff --git a/src/routes/contract/deploy/erc1155.ts b/src/routes/contract/deploy/erc1155.ts index ef58eb8..e2ce965 100644 --- a/src/routes/contract/deploy/erc1155.ts +++ b/src/routes/contract/deploy/erc1155.ts @@ -39,7 +39,7 @@ type ERC1155DeployResponse = { } const ERC1155DeploySchema = { - tags: ['ERC1155', 'Deploy'], + tags: ['Deploy'], description: 'Deploy an ERC1155 contract by providing the default admin, minter and name', body: { diff --git a/src/routes/contract/deploy/erc1155Items.ts b/src/routes/contract/deploy/erc1155Items.ts index a6508a9..0283e7f 100644 --- a/src/routes/contract/deploy/erc1155Items.ts +++ b/src/routes/contract/deploy/erc1155Items.ts @@ -342,8 +342,8 @@ export async function erc1155ItemsDeploy(fastify: FastifyInstance) { ), deployedContractAddress: deployedContractAddress, txSimulationUrls: [ - deploymentSimulationUrl, - initializationSimulationUrl + deploymentSimulationUrl ?? '', + initializationSimulationUrl ?? '' ] } }) diff --git a/src/routes/contract/deploy/erc721.ts b/src/routes/contract/deploy/erc721.ts index ca2acab..9e4dd73 100644 --- a/src/routes/contract/deploy/erc721.ts +++ b/src/routes/contract/deploy/erc721.ts @@ -41,7 +41,7 @@ type ERC721DeployResponse = { } const ERC721DeploySchema = { - tags: ['ERC721', 'Deploy'], + tags: ['Deploy'], description: 'Deploy an ERC721 contract by providing the default admin, minter, name and symbol', body: { diff --git a/src/routes/contract/deploy/erc721Items.ts b/src/routes/contract/deploy/erc721Items.ts index 5b60c9a..c7dec89 100644 --- a/src/routes/contract/deploy/erc721Items.ts +++ b/src/routes/contract/deploy/erc721Items.ts @@ -52,7 +52,7 @@ type ERC721ItemsDeployAndInitializeResponse = { } const ERC721ItemsDeployAndInitializeSchema = { - tags: ['ERC721Items', 'Deploy', 'Initialize', 'Upgradeable'], + tags: ['Deploy', 'Initialize', 'Upgradeable'], description: 'Deploy and initialize an ERC721Items contract.', body: { type: 'object', @@ -361,8 +361,8 @@ export async function erc721ItemsDeployAndInitialize(fastify: FastifyInstance) { ), deployedContractAddress: deployedContractAddress, txSimulationUrls: [ - deploymentSimulationUrl, - initializationSimulationUrl + deploymentSimulationUrl ?? '', + initializationSimulationUrl ?? '' ] } }) diff --git a/src/routes/contract/deploy/upgradeableContract.ts b/src/routes/contract/deploy/upgradeableContract.ts index a20c9f0..975bd21 100644 --- a/src/routes/contract/deploy/upgradeableContract.ts +++ b/src/routes/contract/deploy/upgradeableContract.ts @@ -34,7 +34,7 @@ type DeployUpgradeableContractResponse = { initializationTxHash: string | null initializationTxUrl: string | null deployedContractAddress: string | null - txSimulationUrls?: string[] + txSimulationUrls?: string[] | null error?: string } } @@ -343,8 +343,8 @@ export async function deployUpgradeableContract(fastify: FastifyInstance) { ), deployedContractAddress: deployedContractAddress, txSimulationUrls: [ - deploymentSimulationUrl, - initializationSimulationUrl + deploymentSimulationUrl ?? '', + initializationSimulationUrl ?? '' ] } }) diff --git a/src/routes/contract/extensions/erc1155/erc1155Items/write/batchBurn.ts b/src/routes/contract/extensions/erc1155/erc1155Items/write/batchBurn.ts deleted file mode 100644 index d2ae655..0000000 --- a/src/routes/contract/extensions/erc1155/erc1155Items/write/batchBurn.ts +++ /dev/null @@ -1,203 +0,0 @@ -import type { FastifyInstance } from 'fastify' - -import { ethers } from 'ethers' -import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' -import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation -} from '~/routes/contract/utils/tenderly/getSimulationUrl' -import { TransactionService } from '~/services/transaction.service' -import type { TransactionResponse } from '~/types/general' -import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' -import { getSigner } from '~/utils/wallet' - -type ERC1155ItemsBatchBurnRequestBody = { - tokenIds: string[] - amounts: string[] - waitForReceipt?: boolean -} - -type ERC1155ItemsBatchBurnRequestParams = { - chainId: string - contractAddress: string -} - -type ERC1155ItemsBatchBurnResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } -} - -const ERC1155ItemsBatchBurnSchema = { - tags: ['ERC1155Items'], - body: { - type: 'object', - required: ['tokenIds', 'amounts'], - properties: { - tokenIds: { type: 'array', items: { type: 'string' } }, - amounts: { type: 'array', items: { type: 'string' } }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - } - } -} - -export async function erc1155ItemsBatchBurn(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC1155ItemsBatchBurnRequestParams - Body: ERC1155ItemsBatchBurnRequestBody - Reply: ERC1155ItemsBatchBurnResponse - }>( - '/write/erc1155Items/:chainId/:contractAddress/batchBurn', - { schema: ERC1155ItemsBatchBurnSchema }, - async (request, reply) => { - logRequest(request) - - const tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { tokenIds, amounts, waitForReceipt } = request.body - - const signer = await getSigner(chainId) - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - - const contract = new ethers.Contract( - contractAddress, - erc1155ItemsAbi, - signer - ) - - // Convert string arrays to BigInt arrays for the contract call - const tokenIdsBigInt = tokenIds.map((id) => BigInt(id)) - const amountsBigInt = amounts.map((a) => BigInt(a)) - - const callData = contract.interface.encodeFunctionData('batchBurn', [ - tokenIdsBigInt, - amountsBigInt - ]) - - const tx = { - to: contractAddress, - data: callData - } - logStep(request, 'Tx prepared', { tx }) - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - const tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - const txService = new TransactionService(fastify) - - logStep(request, 'Sending batchBurn transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - tx, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'BatchBurn transaction sent', { txResponse }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc1155ItemsAbi, - data: tx.data, - txHash: txHash, - isDeployTx: false, - args: [JSON.stringify(tokenIds), JSON.stringify(amounts)], - functionName: 'batchBurn' - }) - - logStep(request, 'Batch burn transaction success', { - txHash: txResponse.hash - }) - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error - ? error.message - : 'Failed to batchBurn ERC1155Items' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc1155/erc1155Items/write/burn.ts b/src/routes/contract/extensions/erc1155/erc1155Items/write/burn.ts deleted file mode 100644 index 198f65e..0000000 --- a/src/routes/contract/extensions/erc1155/erc1155Items/write/burn.ts +++ /dev/null @@ -1,212 +0,0 @@ -import type { FastifyInstance } from 'fastify' - -import { ethers } from 'ethers' -import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' -import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation -} from '~/routes/contract/utils/tenderly/getSimulationUrl' -import { TransactionService } from '~/services/transaction.service' -import type { TransactionResponse } from '~/types/general' -import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' -import { getSigner } from '~/utils/wallet' - -type ERC1155ItemsBurnRequestBody = { - tokenId: string - amount: string - waitForReceipt?: boolean -} - -type ERC1155ItemsBurnRequestParams = { - chainId: string - contractAddress: string -} - -type ERC1155ItemsBurnResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } -} - -const ERC1155ItemsBurnSchema = { - tags: ['ERC1155Items'], - description: 'Burns a specific token on an ERC1155Items contract.', - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - body: { - type: 'object', - required: ['tokenId', 'amount'], - properties: { - tokenId: { type: 'string', description: 'The ID of the token to burn.' }, - amount: { type: 'string' }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - error: { type: 'string', nullable: true } - } - } - } - }, - 500: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - } - } -} - -export async function erc1155ItemsBurn(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC1155ItemsBurnRequestParams - Body: ERC1155ItemsBurnRequestBody - Reply: ERC1155ItemsBurnResponse - }>( - '/write/erc1155Items/:chainId/:contractAddress/burn', - { - schema: ERC1155ItemsBurnSchema - }, - async (request, reply) => { - logRequest(request) - - const tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { tokenId, amount, waitForReceipt } = request.body - - const signer = await getSigner(chainId) - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - - const contract = new ethers.Contract( - contractAddress, - erc1155ItemsAbi, - signer - ) - - const callData = contract.interface.encodeFunctionData('burn', [ - tokenId, - amount - ]) - - const tx = { - to: contractAddress, - data: callData - } - logStep(request, 'Tx prepared', { tx }) - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - const tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - const txService = new TransactionService(fastify) - - logStep(request, 'Sending burn transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - tx, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'Burn transaction sent', { txResponse }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc1155ItemsAbi, - data: tx.data, - txHash: txHash, - isDeployTx: false, - args: [tokenId, amount], - functionName: 'burn' - }) - - logStep(request, 'Burn transaction success', { - txHash: txResponse.hash - }) - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error ? error.message : 'Unknown error during burn' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc1155/erc1155Items/write/initialize.ts b/src/routes/contract/extensions/erc1155/erc1155Items/write/initialize.ts deleted file mode 100644 index 260cd3d..0000000 --- a/src/routes/contract/extensions/erc1155/erc1155Items/write/initialize.ts +++ /dev/null @@ -1,301 +0,0 @@ -import type { FastifyInstance } from 'fastify' - -import { - type Abi, - encodeFunctionData, - numberToHex, - pad, - zeroAddress -} from 'viem' -import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' -import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation -} from '~/routes/contract/utils/tenderly/getSimulationUrl' -import { TransactionService } from '~/services/transaction.service' -import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' -import { getSigner } from '~/utils/wallet' - -type ERC1155ItemsInitializeRequestBody = { - owner: string - tokenName: string - tokenBaseURI: string - tokenContractURI: string - royaltyReceiver: string - royaltyFeeNumerator: string - implicitModeValidator: string | undefined | null - implicitModeProjectId: string | undefined | null - waitForReceipt?: boolean -} - -type ERC1155ItemsInitializeRequestParams = { - chainId: string - contractAddress: string -} - -type ERC1155ItemsInitializeResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } -} - -const ERC1155ItemsInitializeSchema = { - tags: ['ERC1155Items'], - description: 'Calls the initialize function on an ERC1155Items contract.', - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - body: { - type: 'object', - required: [ - 'owner', - 'tokenName', - 'tokenBaseURI', - 'tokenContractURI', - 'royaltyReceiver', - 'royaltyFeeNumerator', - 'implicitModeValidator', - 'implicitModeProjectId' - ], - properties: { - owner: { type: 'string', description: 'Address of the contract owner' }, - tokenName: { type: 'string', description: 'Name of the token' }, - tokenBaseURI: { - type: 'string', - description: 'Base URI for token metadata' - }, - tokenContractURI: { - type: 'string', - description: 'Contract URI for collection metadata' - }, - royaltyReceiver: { - type: 'string', - description: 'Address to receive royalties' - }, - royaltyFeeNumerator: { - type: 'string', - description: 'Royalty fee numerator (e.g., 500 for 5%)' - }, - implicitModeValidator: { - type: 'string', - description: 'Address of the implicit mode validator', - nullable: true - }, - implicitModeProjectId: { - type: 'string', - description: 'Implicit mode project ID', - nullable: true - }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - }, - 400: { - type: 'object', - properties: { - error: { type: 'string' } - } - }, - 500: { - type: 'object', - properties: { - error: { type: 'string' } - } - } - } -} - -export async function erc1155ItemsInitialize(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC1155ItemsInitializeRequestParams - Body: ERC1155ItemsInitializeRequestBody - Reply: ERC1155ItemsInitializeResponse - }>( - '/write/erc1155Items/:chainId/:contractAddress/initialize', - { - schema: ERC1155ItemsInitializeSchema - }, - async (request, reply) => { - logRequest(request) - - const tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { - owner, - tokenName, - tokenBaseURI, - tokenContractURI, - royaltyReceiver, - royaltyFeeNumerator, - implicitModeValidator, - implicitModeProjectId, - waitForReceipt - } = request.body - - const signer = await getSigner(chainId) - if (!signer || !signer.account?.address) { - logError(request, new Error('Signer not configured correctly.'), { - signer - }) - throw new Error('Signer not configured correctly.') - } - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - const txService = new TransactionService(fastify) - - const initializeData = encodeFunctionData({ - abi: erc1155ItemsAbi as Abi, - functionName: 'initialize', - args: [ - owner, - tokenName, - tokenBaseURI, - tokenContractURI, - royaltyReceiver, - BigInt(royaltyFeeNumerator), - implicitModeValidator ?? zeroAddress, - pad(numberToHex(Number(implicitModeProjectId ?? 0)), { size: 32 }) - ] - }) - logStep(request, 'Function data encoded', { - owner, - tokenName, - tokenBaseURI, - tokenContractURI, - royaltyReceiver, - royaltyFeeNumerator, - implicitModeValidator, - implicitModeProjectId - }) - - const tx = { - to: contractAddress, - data: initializeData - } - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - const tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - logStep(request, 'Sending initialize transaction...', { - contractAddress, - chainId - }) - const txResponse = await signer.sendTransaction(tx, { - waitForReceipt: waitForReceipt ?? false - }) - txHash = txResponse.hash - logStep(request, 'Initialize transaction sent', { - txHash: txResponse.hash - }) - - if (txResponse.receipt?.status === 0) { - logError(request, new Error('Initialize transaction reverted'), { - txHash: txResponse.receipt?.hash - }) - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc1155ItemsAbi, - data: initializeData, - txHash: txHash, - functionName: 'initialize', - args: [ - owner, - tokenName, - tokenBaseURI, - tokenContractURI, - royaltyReceiver, - royaltyFeeNumerator, - implicitModeValidator ?? zeroAddress, - pad(numberToHex(Number(implicitModeProjectId ?? 0)), { size: 32 }) - ], - isDeployTx: false - }) - logStep(request, 'Transaction saved in db', { txHash: txHash }) - - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error - ? error.message - : 'Unknown error during initialization' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc1155/erc1155Items/write/mint.ts b/src/routes/contract/extensions/erc1155/erc1155Items/write/mint.ts deleted file mode 100644 index bc6b683..0000000 --- a/src/routes/contract/extensions/erc1155/erc1155Items/write/mint.ts +++ /dev/null @@ -1,195 +0,0 @@ -import type { FastifyInstance } from 'fastify' - -import { ethers } from 'ethers' -import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' -import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation -} from '~/routes/contract/utils/tenderly/getSimulationUrl' -import { TransactionService } from '~/services/transaction.service' -import type { TransactionResponse } from '~/types/general' -import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' -import { getSigner } from '~/utils/wallet' - -type ERC1155ItemsMintRequestBody = { - to: string - tokenId: string - amount: string - data?: string - waitForReceipt?: boolean -} - -type ERC1155ItemsMintRequestParams = { - chainId: string - contractAddress: string -} - -type ERC1155ItemsMintResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } -} - -const ERC1155ItemsMintSchema = { - tags: ['ERC1155Items'], - body: { - type: 'object', - required: ['to', 'tokenId', 'amount'], - properties: { - to: { type: 'string' }, - tokenId: { type: 'string' }, - amount: { type: 'string' }, - data: { type: 'string', nullable: true }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - } - } -} - -export async function erc1155ItemsMint(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC1155ItemsMintRequestParams - Body: ERC1155ItemsMintRequestBody - Reply: ERC1155ItemsMintResponse - }>( - '/write/erc1155Items/:chainId/:contractAddress/mint', - { schema: ERC1155ItemsMintSchema }, - async (request, reply) => { - logRequest(request) - - const tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { to, tokenId, amount, data, waitForReceipt } = request.body - - const signer = await getSigner(chainId) - const contract = new ethers.Contract( - contractAddress, - erc1155ItemsAbi, - signer - ) - - const callData = contract.interface.encodeFunctionData('mint', [ - to, - tokenId, - amount, - data ?? '0x' - ]) - - const tx = { - to: contractAddress, - data: callData - } - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - const tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - contractAddress: signedTx.entrypoint, - blockIndex: 0, - rawFunctionInput: simulationData - }) - - const txService = new TransactionService(fastify) - - logStep(request, 'Sending mint transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - tx, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'Mint transaction sent', { txResponse }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc1155ItemsAbi, - data: tx.data, - txHash: txHash, - isDeployTx: false, - args: [to, tokenId, amount, data ?? '0x'], - functionName: 'mint' - }) - - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error ? error.message : 'Failed to mint ERC1155Items' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc1155/index.ts b/src/routes/contract/extensions/erc1155/index.ts new file mode 100644 index 0000000..a620f65 --- /dev/null +++ b/src/routes/contract/extensions/erc1155/index.ts @@ -0,0 +1,21 @@ +import type { FastifyInstance } from 'fastify' + +import { erc1155SafeBatchTransferFrom } from './write/safeBatchTransferFrom' +import { erc1155SafeTransferFrom } from './write/safeTransferFrom' +import { erc1155SetApprovalForAll } from './write/setApprovalForAll' +import { erc1155BalanceOf } from './read/balanceOf' +import { erc1155BalanceOfBatch } from './read/balanceOfBatch' +import { erc1155IsApprovedForAll } from './read/isApprovedForAll' +import { erc1155SupportsInterface } from './read/supportsInterface' +import { erc1155Uri } from './read/uri' + +export function registerErc1155Routes(fastify: FastifyInstance) { + erc1155SafeBatchTransferFrom(fastify) + erc1155SafeTransferFrom(fastify) + erc1155SetApprovalForAll(fastify) + erc1155BalanceOf(fastify) + erc1155BalanceOfBatch(fastify) + erc1155IsApprovedForAll(fastify) + erc1155SupportsInterface(fastify) + erc1155Uri(fastify) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155/read/balanceOf.ts b/src/routes/contract/extensions/erc1155/read/balanceOf.ts index 1c2724d..14d39b4 100644 --- a/src/routes/contract/extensions/erc1155/read/balanceOf.ts +++ b/src/routes/contract/extensions/erc1155/read/balanceOf.ts @@ -1,98 +1,121 @@ -import { ethers } from 'ethers' import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' import { erc1155Abi } from '~/constants/abis/erc1155' import { getSigner } from '~/utils/wallet' -type ERC1155BalanceOfRequestQuery = { - account: string - id: string +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155BalanceOfRequestQuery = { + account: string, id: string } -type ERC1155BalanceOfRequestParams = { - chainId: string - contractAddress: string +type erc1155BalanceOfRequestParams = { + chainId: string + contractAddress: string } -type ERC1155BalanceOfResponse = { - result?: { - data: unknown - error?: string - } +type erc1155BalanceOfResponse = { + result?: { + data: any + error?: string + } } -const ERC1155BalanceOfSchema = { - tags: ['ERC1155'], - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - query: { - type: 'object', - required: ['account', 'id'], - properties: { - account: { type: 'string' }, - id: { type: 'string' } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - data: { type: 'string' }, - error: { type: 'string', nullable: true } - } - } - } - } - } +const erc1155BalanceOfSchema = { + tags: ['erc1155'], + querystring: { + type: 'object', + required: ['account', 'id'], + properties: { + account: { type: 'string' }, + id: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } } export async function erc1155BalanceOf(fastify: FastifyInstance) { - fastify.get<{ - Params: ERC1155BalanceOfRequestParams - Querystring: ERC1155BalanceOfRequestQuery - Reply: ERC1155BalanceOfResponse - }>( - '/read/erc1155/:chainId/:contractAddress/balanceOf', - { - schema: ERC1155BalanceOfSchema - }, - async (request, reply) => { - try { - const { chainId, contractAddress } = request.params - const { account, id } = request.query + fastify.get<{ + Params: erc1155BalanceOfRequestParams + Querystring: erc1155BalanceOfRequestQuery + Reply: erc1155BalanceOfResponse + }>( + '/read/erc1155/:chainId/:contractAddress/balanceOf', + { schema: erc1155BalanceOfSchema }, + async (request, reply) => { + const { account, id } = request.query + const { chainId, contractAddress } = request.params - const provider = await getSigner(chainId) - const contract = new ethers.Contract( - contractAddress, - erc1155Abi, - provider - ) + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } - const data = await contract.balanceOf(account, id) + const contract = new ethers.Contract( + contractAddress, + erc1155Abi, + signer + ) - return reply.code(200).send({ - result: { - data: data.toString() - } - }) - } catch (error) { - request.log.error(error) - return reply.code(500).send({ - result: { - data: null, - error: - error instanceof Error ? error.message : 'Failed to read balance' - } - }) - } - } - ) -} + const result = await contract.balanceOf(account, id) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155/read/balanceOfBatch.ts b/src/routes/contract/extensions/erc1155/read/balanceOfBatch.ts new file mode 100644 index 0000000..5e88cbf --- /dev/null +++ b/src/routes/contract/extensions/erc1155/read/balanceOfBatch.ts @@ -0,0 +1,121 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155Abi } from '~/constants/abis/erc1155' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155BalanceOfBatchRequestQuery = { + accounts: string, ids: string +} + +type erc1155BalanceOfBatchRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155BalanceOfBatchResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155BalanceOfBatchSchema = { + tags: ['erc1155'], + querystring: { + type: 'object', + required: ['accounts', 'ids'], + properties: { + accounts: { type: 'string' }, + ids: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155BalanceOfBatch(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155BalanceOfBatchRequestParams + Querystring: erc1155BalanceOfBatchRequestQuery + Reply: erc1155BalanceOfBatchResponse + }>( + '/read/erc1155/:chainId/:contractAddress/balanceOfBatch', + { schema: erc1155BalanceOfBatchSchema }, + async (request, reply) => { + const { accounts, ids } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155Abi, + signer + ) + + const result = await contract.balanceOfBatch(accounts, ids) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155/read/hasRole.ts b/src/routes/contract/extensions/erc1155/read/hasRole.ts deleted file mode 100644 index 5095071..0000000 --- a/src/routes/contract/extensions/erc1155/read/hasRole.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { ethers } from 'ethers' -import type { FastifyInstance } from 'fastify' -import { erc1155Abi } from '~/constants/abis/erc1155' -import { getSigner } from '~/utils/wallet' - -type ERC1155HasRoleRequestQuery = { - role: string - account: string -} - -type ERC1155HasRoleRequestParams = { - chainId: string - contractAddress: string -} - -type ERC1155HasRoleResponse = { - result?: { - data: unknown - error?: string - } -} - -const ERC1155HasRoleSchema = { - tags: ['ERC1155'], - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - query: { - type: 'object', - required: ['role', 'account'], - properties: { - role: { type: 'string' }, - account: { type: 'string' } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - data: { type: 'string' }, - error: { type: 'string', nullable: true } - } - } - } - } - } -} - -export async function erc1155HasRole(fastify: FastifyInstance) { - fastify.get<{ - Params: ERC1155HasRoleRequestParams - Querystring: ERC1155HasRoleRequestQuery - Reply: ERC1155HasRoleResponse - }>( - '/read/erc1155/:chainId/:contractAddress/hasRole', - { - schema: ERC1155HasRoleSchema - }, - async (request, reply) => { - try { - const { chainId, contractAddress } = request.params - const { role, account } = request.query - - const provider = await getSigner(chainId) - const contract = new ethers.Contract( - contractAddress, - erc1155Abi, - provider - ) - - const data = await contract.hasRole(role, account) - console.log('Has role ', role, ' for account ', account, ' is ', data) - - return reply.code(200).send({ - result: { - data: data.toString() - } - }) - } catch (error) { - request.log.error(error) - return reply.code(500).send({ - result: { - data: null, - error: - error instanceof Error ? error.message : 'Failed to read balance' - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc1155/read/isApprovedForAll.ts b/src/routes/contract/extensions/erc1155/read/isApprovedForAll.ts new file mode 100644 index 0000000..91953e0 --- /dev/null +++ b/src/routes/contract/extensions/erc1155/read/isApprovedForAll.ts @@ -0,0 +1,121 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155Abi } from '~/constants/abis/erc1155' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155IsApprovedForAllRequestQuery = { + account: string, operator: string +} + +type erc1155IsApprovedForAllRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155IsApprovedForAllResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155IsApprovedForAllSchema = { + tags: ['erc1155'], + querystring: { + type: 'object', + required: ['account', 'operator'], + properties: { + account: { type: 'string' }, + operator: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155IsApprovedForAll(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155IsApprovedForAllRequestParams + Querystring: erc1155IsApprovedForAllRequestQuery + Reply: erc1155IsApprovedForAllResponse + }>( + '/read/erc1155/:chainId/:contractAddress/isApprovedForAll', + { schema: erc1155IsApprovedForAllSchema }, + async (request, reply) => { + const { account, operator } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155Abi, + signer + ) + + const result = await contract.isApprovedForAll(account, operator) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155/read/minterRole.ts b/src/routes/contract/extensions/erc1155/read/minterRole.ts deleted file mode 100644 index 57bc5c6..0000000 --- a/src/routes/contract/extensions/erc1155/read/minterRole.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { ethers } from 'ethers' -import type { FastifyInstance } from 'fastify' -import { erc1155Abi } from '~/constants/abis/erc1155' -import { getSigner } from '~/utils/wallet' - -type ERC1155MinterRoleRequestParams = { - chainId: string - contractAddress: string -} - -type ERC1155MinterRoleResponse = { - result?: { - data: unknown - error?: string - } -} - -const ERC1155MinterRoleSchema = { - tags: ['ERC1155'], - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - data: { type: 'string' }, - error: { type: 'string', nullable: true } - } - } - } - } - } -} - -export async function erc1155MinterRole(fastify: FastifyInstance) { - fastify.get<{ - Params: ERC1155MinterRoleRequestParams - Reply: ERC1155MinterRoleResponse - }>( - '/read/erc1155/:chainId/:contractAddress/minterRole', - { - schema: ERC1155MinterRoleSchema - }, - async (request, reply) => { - try { - const { chainId, contractAddress } = request.params - - const provider = await getSigner(chainId) - const contract = new ethers.Contract( - contractAddress, - erc1155Abi, - provider - ) - - const data = await contract.MINTER_ROLE() - - return reply.code(200).send({ - result: { - data: data.toString() - } - }) - } catch (error) { - request.log.error(error) - return reply.code(500).send({ - result: { - data: null, - error: - error instanceof Error ? error.message : 'Failed to read balance' - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc1155/read/supportsInterface.ts b/src/routes/contract/extensions/erc1155/read/supportsInterface.ts new file mode 100644 index 0000000..05b544c --- /dev/null +++ b/src/routes/contract/extensions/erc1155/read/supportsInterface.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155Abi } from '~/constants/abis/erc1155' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155SupportsInterfaceRequestQuery = { + interfaceId: string +} + +type erc1155SupportsInterfaceRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155SupportsInterfaceResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155SupportsInterfaceSchema = { + tags: ['erc1155'], + querystring: { + type: 'object', + required: ['interfaceId'], + properties: { + interfaceId: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155SupportsInterface(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155SupportsInterfaceRequestParams + Querystring: erc1155SupportsInterfaceRequestQuery + Reply: erc1155SupportsInterfaceResponse + }>( + '/read/erc1155/:chainId/:contractAddress/supportsInterface', + { schema: erc1155SupportsInterfaceSchema }, + async (request, reply) => { + const { interfaceId } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155Abi, + signer + ) + + const result = await contract.supportsInterface(interfaceId) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155/read/uri.ts b/src/routes/contract/extensions/erc1155/read/uri.ts new file mode 100644 index 0000000..b5b179b --- /dev/null +++ b/src/routes/contract/extensions/erc1155/read/uri.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155Abi } from '~/constants/abis/erc1155' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155UriRequestQuery = { + id: string +} + +type erc1155UriRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155UriResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155UriSchema = { + tags: ['erc1155'], + querystring: { + type: 'object', + required: ['id'], + properties: { + id: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155Uri(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155UriRequestParams + Querystring: erc1155UriRequestQuery + Reply: erc1155UriResponse + }>( + '/read/erc1155/:chainId/:contractAddress/uri', + { schema: erc1155UriSchema }, + async (request, reply) => { + const { id } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155Abi, + signer + ) + + const result = await contract.uri(id) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155/write/grantRole.ts b/src/routes/contract/extensions/erc1155/write/grantRole.ts deleted file mode 100644 index 0e60501..0000000 --- a/src/routes/contract/extensions/erc1155/write/grantRole.ts +++ /dev/null @@ -1,219 +0,0 @@ -import type { FastifyInstance } from 'fastify' - -import { ethers } from 'ethers' -import { erc1155Abi } from '~/constants/abis/erc1155' -import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation -} from '~/routes/contract/utils/tenderly/getSimulationUrl' -import { TransactionService } from '~/services/transaction.service' -import type { TransactionResponse } from '~/types/general' -import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' -import { getSigner } from '~/utils/wallet' - -type ERC1155GrantRoleRequestBody = { - role: string - account: string - waitForReceipt?: boolean -} - -type ERC1155GrantRoleRequestParams = { - chainId: string - contractAddress: string -} - -type ERC1155GrantRoleResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } -} - -const ERC1155GrantRoleSchema = { - tags: ['ERC1155'], - body: { - type: 'object', - required: ['role', 'account'], - properties: { - role: { type: 'string' }, - account: { type: 'string' }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - }, - 500: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string', nullable: true }, - txUrl: { type: 'string', nullable: true }, - error: { type: 'string' } - } - } - } - } - } -} - -export async function erc1155GrantRole(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC1155GrantRoleRequestParams - Body: ERC1155GrantRoleRequestBody - Reply: ERC1155GrantRoleResponse - }>( - '/write/erc1155/:chainId/:contractAddress/grantRole', - { - schema: ERC1155GrantRoleSchema - }, - async (request, reply) => { - logRequest(request) - - let tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { role, account, waitForReceipt } = request.body - - const signer = await getSigner(chainId) - if (!signer || !signer.account?.address) { - logError(request, new Error('Signer not configured correctly.'), { - signer - }) - throw new Error('Signer not configured correctly.') - } - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - - const contract = new ethers.Contract( - contractAddress, - erc1155Abi, - signer - ) - logStep(request, 'Contract instance created') - - const data = contract.interface.encodeFunctionData('grantRole', [ - role, - account - ]) - logStep(request, 'Function data encoded', { role, account }) - - const tx = { - to: contractAddress, - data - } - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - const txService = new TransactionService(fastify) - - logStep(request, 'Sending grantRole transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - tx, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'GrantRole transaction sent', { txResponse }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc1155Abi, - data: tx.data, - txHash: txHash, - isDeployTx: false, - args: [role, account], - functionName: 'grantRole' - }) - - logStep(request, 'GrantRole transaction success', { - txHash: txHash - }) - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error ? error.message : 'Failed to grant role' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc1155/write/mint.ts b/src/routes/contract/extensions/erc1155/write/mint.ts deleted file mode 100644 index d3322ac..0000000 --- a/src/routes/contract/extensions/erc1155/write/mint.ts +++ /dev/null @@ -1,225 +0,0 @@ -import type { FastifyInstance } from 'fastify' - -import { ethers } from 'ethers' -import { erc1155Abi } from '~/constants/abis/erc1155' -import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation -} from '~/routes/contract/utils/tenderly/getSimulationUrl' -import { TransactionService } from '~/services/transaction.service' -import type { TransactionResponse } from '~/types/general' -import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' -import { getSigner } from '~/utils/wallet' - -type ERC1155MintRequestBody = { - recipient: string - id: string - amount: string - data: string - waitForReceipt?: boolean -} - -type ERC1155MintRequestParams = { - chainId: string - contractAddress: string -} - -type ERC1155MintResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } -} - -const ERC1155MintSchema = { - tags: ['ERC1155'], - body: { - type: 'object', - required: ['recipient', 'id', 'amount', 'data'], - properties: { - recipient: { type: 'string' }, - id: { type: 'string' }, - amount: { type: 'string' }, - data: { type: 'string' }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - }, - 500: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string', nullable: true }, - txUrl: { type: 'string', nullable: true }, - error: { type: 'string' } - } - } - } - } - } -} - -export async function erc1155Mint(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC1155MintRequestParams - Body: ERC1155MintRequestBody - Reply: ERC1155MintResponse - }>( - '/write/erc1155/:chainId/:contractAddress/mint', - { - schema: ERC1155MintSchema - }, - async (request, reply) => { - logRequest(request) - - let tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { - recipient, - id, - amount, - data: mintData, - waitForReceipt - } = request.body - - const signer = await getSigner(chainId) - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - - const contract = new ethers.Contract( - contractAddress, - erc1155Abi, - signer - ) - - const data = contract.interface.encodeFunctionData('mint', [ - recipient, - id, - amount, - mintData - ]) - - const tx = { - to: contractAddress, - data - } - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - logStep(request, 'Tx prepared', { tx }) - - const txService = new TransactionService(fastify) - - logStep(request, 'Sending mint transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - tx, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'Mint transaction sent', { txResponse }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc1155Abi, - data: tx.data, - txHash: txHash, - isDeployTx: false, - args: [recipient, id, amount, mintData], - functionName: 'mint' - }) - - logStep(request, 'Mint transaction success', { - txHash: txResponse.hash - }) - return reply.code(200).send({ - result: { - txHash: txResponse.hash, - txUrl: getBlockExplorerUrl(Number(chainId), txResponse.hash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error ? error.message : 'Failed to mint NFT' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc1155/write/mintBatch.ts b/src/routes/contract/extensions/erc1155/write/mintBatch.ts deleted file mode 100644 index d59bccf..0000000 --- a/src/routes/contract/extensions/erc1155/write/mintBatch.ts +++ /dev/null @@ -1,222 +0,0 @@ -import type { FastifyInstance } from 'fastify' - -import { ethers } from 'ethers' -import { erc1155Abi } from '~/constants/abis/erc1155' -import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation -} from '~/routes/contract/utils/tenderly/getSimulationUrl' -import { TransactionService } from '~/services/transaction.service' -import type { TransactionResponse } from '~/types/general' -import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' -import { getSigner } from '~/utils/wallet' - -type ERC1155MintBatchRequestBody = { - recipients: string[] - ids: string[] - amounts: string[] - datas: string[] - waitForReceipt?: boolean -} - -type ERC1155MintBatchRequestParams = { - chainId: string - contractAddress: string -} - -type ERC1155MintBatchResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } -} - -const ERC1155MintBatchSchema = { - tags: ['ERC1155'], - body: { - type: 'object', - required: ['recipients', 'ids', 'amounts', 'datas'], - properties: { - recipients: { type: 'array', items: { type: 'string' } }, - ids: { type: 'array', items: { type: 'string' } }, - amounts: { type: 'array', items: { type: 'string' } }, - datas: { type: 'array', items: { type: 'string' } }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - }, - 500: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string', nullable: true }, - txUrl: { type: 'string', nullable: true }, - error: { type: 'string' } - } - } - } - } - } -} - -export async function erc1155MintBatch(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC1155MintBatchRequestParams - Body: ERC1155MintBatchRequestBody - Reply: ERC1155MintBatchResponse - }>( - '/write/erc1155/:chainId/:contractAddress/mintBatch', - { - schema: ERC1155MintBatchSchema - }, - async (request, reply) => { - logRequest(request) - - let tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { recipients, ids, amounts, datas, waitForReceipt } = request.body - - const signer = await getSigner(chainId) - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - - const contract = new ethers.Contract( - contractAddress, - erc1155Abi, - signer - ) - - const txs = recipients.map((account: string, index: number) => { - const data = contract.interface.encodeFunctionData('mint', [ - account, - ids[index], - amounts[index], - datas[index] - ]) - return { - to: contractAddress, - data - } - }) - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - txs, - Number(chainId) - ) - tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - logStep(request, 'Transactions prepared', { txs }) - - const txService = new TransactionService(fastify) - - logStep(request, 'Sending mintBatch transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - txs, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'MintBatch transaction sent', { txResponse }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - txs.forEach(async (tx, index) => { - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc1155Abi, - data: tx.data, - txHash: txHash ?? '', - isDeployTx: false, - args: [recipients[index], ids[index], amounts[index], datas[index]], - functionName: 'mint' - }) - }) - - logStep(request, 'MintBatch transaction success', { - txHash: txHash - }) - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error ? error.message : 'Failed to mint NFT' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc1155/write/safeBatchTransferFrom.ts b/src/routes/contract/extensions/erc1155/write/safeBatchTransferFrom.ts new file mode 100644 index 0000000..f15a3f9 --- /dev/null +++ b/src/routes/contract/extensions/erc1155/write/safeBatchTransferFrom.ts @@ -0,0 +1,191 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155Abi } from '~/constants/abis/erc1155' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155SafeBatchTransferFromRequestBody = { + from: string, to: string, ids: string[], amounts: string[], data: string, + waitForReceipt?: boolean +} + +type erc1155SafeBatchTransferFromRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155SafeBatchTransferFromResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155SafeBatchTransferFromSchema = { + tags: ['erc1155'], + body: { + type: 'object', + required: ['from', 'to', 'ids', 'amounts', 'data'], + properties: { + from: { type: 'string' }, + to: { type: 'string' }, + ids: { type: 'array', items: { type: 'string' } }, + amounts: { type: 'array', items: { type: 'string' } }, + data: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155SafeBatchTransferFrom(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155SafeBatchTransferFromRequestParams + Body: erc1155SafeBatchTransferFromRequestBody + Reply: erc1155SafeBatchTransferFromResponse + }>( + '/write/erc1155/:chainId/:contractAddress/safeBatchTransferFrom', + { schema: erc1155SafeBatchTransferFromSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { from, to, ids, amounts, data, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('safeBatchTransferFrom', [ + from, to, ids.map((v) => BigInt(v)), amounts.map((v) => BigInt(v)), data + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending safeBatchTransferFrom transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SafeBatchTransferFrom transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [from, to, ids.map(String), amounts.map(String), data], + functionName: 'safeBatchTransferFrom' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155/write/safeTransferFrom.ts b/src/routes/contract/extensions/erc1155/write/safeTransferFrom.ts new file mode 100644 index 0000000..20fffe2 --- /dev/null +++ b/src/routes/contract/extensions/erc1155/write/safeTransferFrom.ts @@ -0,0 +1,191 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155Abi } from '~/constants/abis/erc1155' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155SafeTransferFromRequestBody = { + from: string, to: string, id: string, amount: string, data: string, + waitForReceipt?: boolean +} + +type erc1155SafeTransferFromRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155SafeTransferFromResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155SafeTransferFromSchema = { + tags: ['erc1155'], + body: { + type: 'object', + required: ['from', 'to', 'id', 'amount', 'data'], + properties: { + from: { type: 'string' }, + to: { type: 'string' }, + id: { type: 'string' }, + amount: { type: 'string' }, + data: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155SafeTransferFrom(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155SafeTransferFromRequestParams + Body: erc1155SafeTransferFromRequestBody + Reply: erc1155SafeTransferFromResponse + }>( + '/write/erc1155/:chainId/:contractAddress/safeTransferFrom', + { schema: erc1155SafeTransferFromSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { from, to, id, amount, data, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('safeTransferFrom', [ + from, to, BigInt(id), BigInt(amount), data + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending safeTransferFrom transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SafeTransferFrom transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [from, to, String(id), String(amount), data], + functionName: 'safeTransferFrom' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155/write/setApprovalForAll.ts b/src/routes/contract/extensions/erc1155/write/setApprovalForAll.ts new file mode 100644 index 0000000..3de4b81 --- /dev/null +++ b/src/routes/contract/extensions/erc1155/write/setApprovalForAll.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155Abi } from '~/constants/abis/erc1155' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155SetApprovalForAllRequestBody = { + operator: string, approved: string, + waitForReceipt?: boolean +} + +type erc1155SetApprovalForAllRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155SetApprovalForAllResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155SetApprovalForAllSchema = { + tags: ['erc1155'], + body: { + type: 'object', + required: ['operator', 'approved'], + properties: { + operator: { type: 'string' }, + approved: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155SetApprovalForAll(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155SetApprovalForAllRequestParams + Body: erc1155SetApprovalForAllRequestBody + Reply: erc1155SetApprovalForAllResponse + }>( + '/write/erc1155/:chainId/:contractAddress/setApprovalForAll', + { schema: erc1155SetApprovalForAllSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { operator, approved, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setApprovalForAll', [ + operator, approved + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setApprovalForAll transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetApprovalForAll transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [operator, approved], + functionName: 'setApprovalForAll' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/index.ts b/src/routes/contract/extensions/erc1155Items/index.ts new file mode 100644 index 0000000..e2f2449 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/index.ts @@ -0,0 +1,75 @@ +import type { FastifyInstance } from 'fastify' + +import { erc1155ItemsBatchBurn } from './write/batchBurn' +import { erc1155ItemsBatchMint } from './write/batchMint' +import { erc1155ItemsBurn } from './write/burn' +import { erc1155ItemsGrantRole } from './write/grantRole' +import { erc1155ItemsInitialize } from './write/initialize' +import { erc1155ItemsMint } from './write/mint' +import { erc1155ItemsRenounceRole } from './write/renounceRole' +import { erc1155ItemsRevokeRole } from './write/revokeRole' +import { erc1155ItemsSafeBatchTransferFrom } from './write/safeBatchTransferFrom' +import { erc1155ItemsSafeTransferFrom } from './write/safeTransferFrom' +import { erc1155ItemsSetApprovalForAll } from './write/setApprovalForAll' +import { erc1155ItemsSetBaseMetadataURI } from './write/setBaseMetadataURI' +import { erc1155ItemsSetContractName } from './write/setContractName' +import { erc1155ItemsSetContractURI } from './write/setContractURI' +import { erc1155ItemsSetDefaultRoyalty } from './write/setDefaultRoyalty' +import { erc1155ItemsSetImplicitModeProjectId } from './write/setImplicitModeProjectId' +import { erc1155ItemsSetImplicitModeValidator } from './write/setImplicitModeValidator' +import { erc1155ItemsSetTokenRoyalty } from './write/setTokenRoyalty' +import { erc1155ItemsDEFAULT_ADMIN_ROLE } from './read/DEFAULT_ADMIN_ROLE' +import { erc1155ItemsAcceptImplicitRequest } from './read/acceptImplicitRequest' +import { erc1155ItemsBalanceOf } from './read/balanceOf' +import { erc1155ItemsBalanceOfBatch } from './read/balanceOfBatch' +import { erc1155ItemsBaseURI } from './read/baseURI' +import { erc1155ItemsContractURI } from './read/contractURI' +import { erc1155ItemsGetRoleAdmin } from './read/getRoleAdmin' +import { erc1155ItemsGetRoleMember } from './read/getRoleMember' +import { erc1155ItemsGetRoleMemberCount } from './read/getRoleMemberCount' +import { erc1155ItemsHasRole } from './read/hasRole' +import { erc1155ItemsIsApprovedForAll } from './read/isApprovedForAll' +import { erc1155ItemsName } from './read/name' +import { erc1155ItemsRoyaltyInfo } from './read/royaltyInfo' +import { erc1155ItemsSupportsInterface } from './read/supportsInterface' +import { erc1155ItemsTokenSupply } from './read/tokenSupply' +import { erc1155ItemsTotalSupply } from './read/totalSupply' +import { erc1155ItemsUri } from './read/uri' + +export function registerErc1155ItemsRoutes(fastify: FastifyInstance) { + erc1155ItemsBatchBurn(fastify) + erc1155ItemsBatchMint(fastify) + erc1155ItemsBurn(fastify) + erc1155ItemsGrantRole(fastify) + erc1155ItemsInitialize(fastify) + erc1155ItemsMint(fastify) + erc1155ItemsRenounceRole(fastify) + erc1155ItemsRevokeRole(fastify) + erc1155ItemsSafeBatchTransferFrom(fastify) + erc1155ItemsSafeTransferFrom(fastify) + erc1155ItemsSetApprovalForAll(fastify) + erc1155ItemsSetBaseMetadataURI(fastify) + erc1155ItemsSetContractName(fastify) + erc1155ItemsSetContractURI(fastify) + erc1155ItemsSetDefaultRoyalty(fastify) + erc1155ItemsSetImplicitModeProjectId(fastify) + erc1155ItemsSetImplicitModeValidator(fastify) + erc1155ItemsSetTokenRoyalty(fastify) + erc1155ItemsDEFAULT_ADMIN_ROLE(fastify) + erc1155ItemsAcceptImplicitRequest(fastify) + erc1155ItemsBalanceOf(fastify) + erc1155ItemsBalanceOfBatch(fastify) + erc1155ItemsBaseURI(fastify) + erc1155ItemsContractURI(fastify) + erc1155ItemsGetRoleAdmin(fastify) + erc1155ItemsGetRoleMember(fastify) + erc1155ItemsGetRoleMemberCount(fastify) + erc1155ItemsHasRole(fastify) + erc1155ItemsIsApprovedForAll(fastify) + erc1155ItemsName(fastify) + erc1155ItemsRoyaltyInfo(fastify) + erc1155ItemsSupportsInterface(fastify) + erc1155ItemsTokenSupply(fastify) + erc1155ItemsTotalSupply(fastify) + erc1155ItemsUri(fastify) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/DEFAULT_ADMIN_ROLE.ts b/src/routes/contract/extensions/erc1155Items/read/DEFAULT_ADMIN_ROLE.ts new file mode 100644 index 0000000..fac40f2 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/DEFAULT_ADMIN_ROLE.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsDEFAULT_ADMIN_ROLERequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsDEFAULT_ADMIN_ROLEResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsDEFAULT_ADMIN_ROLESchema = { + tags: ['erc1155Items'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsDEFAULT_ADMIN_ROLE(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsDEFAULT_ADMIN_ROLERequestParams + + Reply: erc1155ItemsDEFAULT_ADMIN_ROLEResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/DEFAULT_ADMIN_ROLE', + { schema: erc1155ItemsDEFAULT_ADMIN_ROLESchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.DEFAULT_ADMIN_ROLE() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/acceptImplicitRequest.ts b/src/routes/contract/extensions/erc1155Items/read/acceptImplicitRequest.ts new file mode 100644 index 0000000..b095a31 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/acceptImplicitRequest.ts @@ -0,0 +1,122 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsAcceptImplicitRequestRequestQuery = { + wallet: string, attestation: string, call: string +} + +type erc1155ItemsAcceptImplicitRequestRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsAcceptImplicitRequestResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsAcceptImplicitRequestSchema = { + tags: ['erc1155Items'], + querystring: { + type: 'object', + required: ['wallet', 'attestation', 'call'], + properties: { + wallet: { type: 'string' }, + attestation: { type: 'string' }, + call: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsAcceptImplicitRequest(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsAcceptImplicitRequestRequestParams + Querystring: erc1155ItemsAcceptImplicitRequestRequestQuery + Reply: erc1155ItemsAcceptImplicitRequestResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/acceptImplicitRequest', + { schema: erc1155ItemsAcceptImplicitRequestSchema }, + async (request, reply) => { + const { wallet, attestation, call } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.acceptImplicitRequest(wallet, attestation, call) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/balanceOf.ts b/src/routes/contract/extensions/erc1155Items/read/balanceOf.ts new file mode 100644 index 0000000..6e7a6f9 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/balanceOf.ts @@ -0,0 +1,121 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsBalanceOfRequestQuery = { + owner: string, id: string +} + +type erc1155ItemsBalanceOfRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsBalanceOfResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsBalanceOfSchema = { + tags: ['erc1155Items'], + querystring: { + type: 'object', + required: ['owner', 'id'], + properties: { + owner: { type: 'string' }, + id: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsBalanceOf(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsBalanceOfRequestParams + Querystring: erc1155ItemsBalanceOfRequestQuery + Reply: erc1155ItemsBalanceOfResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/balanceOf', + { schema: erc1155ItemsBalanceOfSchema }, + async (request, reply) => { + const { owner, id } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.balanceOf(owner, id) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/balanceOfBatch.ts b/src/routes/contract/extensions/erc1155Items/read/balanceOfBatch.ts new file mode 100644 index 0000000..33c4bad --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/balanceOfBatch.ts @@ -0,0 +1,121 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsBalanceOfBatchRequestQuery = { + owners: string, ids: string +} + +type erc1155ItemsBalanceOfBatchRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsBalanceOfBatchResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsBalanceOfBatchSchema = { + tags: ['erc1155Items'], + querystring: { + type: 'object', + required: ['owners', 'ids'], + properties: { + owners: { type: 'string' }, + ids: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsBalanceOfBatch(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsBalanceOfBatchRequestParams + Querystring: erc1155ItemsBalanceOfBatchRequestQuery + Reply: erc1155ItemsBalanceOfBatchResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/balanceOfBatch', + { schema: erc1155ItemsBalanceOfBatchSchema }, + async (request, reply) => { + const { owners, ids } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.balanceOfBatch(owners, ids) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/baseURI.ts b/src/routes/contract/extensions/erc1155Items/read/baseURI.ts new file mode 100644 index 0000000..e9e362a --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/baseURI.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsBaseURIRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsBaseURIResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsBaseURISchema = { + tags: ['erc1155Items'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsBaseURI(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsBaseURIRequestParams + + Reply: erc1155ItemsBaseURIResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/baseURI', + { schema: erc1155ItemsBaseURISchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.baseURI() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/contractURI.ts b/src/routes/contract/extensions/erc1155Items/read/contractURI.ts new file mode 100644 index 0000000..01d1836 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/contractURI.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsContractURIRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsContractURIResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsContractURISchema = { + tags: ['erc1155Items'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsContractURI(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsContractURIRequestParams + + Reply: erc1155ItemsContractURIResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/contractURI', + { schema: erc1155ItemsContractURISchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.contractURI() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/getRoleAdmin.ts b/src/routes/contract/extensions/erc1155Items/read/getRoleAdmin.ts new file mode 100644 index 0000000..40b6d84 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/getRoleAdmin.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsGetRoleAdminRequestQuery = { + role: string +} + +type erc1155ItemsGetRoleAdminRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsGetRoleAdminResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsGetRoleAdminSchema = { + tags: ['erc1155Items'], + querystring: { + type: 'object', + required: ['role'], + properties: { + role: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsGetRoleAdmin(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsGetRoleAdminRequestParams + Querystring: erc1155ItemsGetRoleAdminRequestQuery + Reply: erc1155ItemsGetRoleAdminResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/getRoleAdmin', + { schema: erc1155ItemsGetRoleAdminSchema }, + async (request, reply) => { + const { role } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.getRoleAdmin(role) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/getRoleMember.ts b/src/routes/contract/extensions/erc1155Items/read/getRoleMember.ts new file mode 100644 index 0000000..d471470 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/getRoleMember.ts @@ -0,0 +1,121 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsGetRoleMemberRequestQuery = { + role: string, index: string +} + +type erc1155ItemsGetRoleMemberRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsGetRoleMemberResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsGetRoleMemberSchema = { + tags: ['erc1155Items'], + querystring: { + type: 'object', + required: ['role', 'index'], + properties: { + role: { type: 'string' }, + index: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsGetRoleMember(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsGetRoleMemberRequestParams + Querystring: erc1155ItemsGetRoleMemberRequestQuery + Reply: erc1155ItemsGetRoleMemberResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/getRoleMember', + { schema: erc1155ItemsGetRoleMemberSchema }, + async (request, reply) => { + const { role, index } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.getRoleMember(role, index) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/getRoleMemberCount.ts b/src/routes/contract/extensions/erc1155Items/read/getRoleMemberCount.ts new file mode 100644 index 0000000..15472e4 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/getRoleMemberCount.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsGetRoleMemberCountRequestQuery = { + role: string +} + +type erc1155ItemsGetRoleMemberCountRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsGetRoleMemberCountResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsGetRoleMemberCountSchema = { + tags: ['erc1155Items'], + querystring: { + type: 'object', + required: ['role'], + properties: { + role: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsGetRoleMemberCount(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsGetRoleMemberCountRequestParams + Querystring: erc1155ItemsGetRoleMemberCountRequestQuery + Reply: erc1155ItemsGetRoleMemberCountResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/getRoleMemberCount', + { schema: erc1155ItemsGetRoleMemberCountSchema }, + async (request, reply) => { + const { role } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.getRoleMemberCount(role) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/hasRole.ts b/src/routes/contract/extensions/erc1155Items/read/hasRole.ts new file mode 100644 index 0000000..f4cef74 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/hasRole.ts @@ -0,0 +1,121 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsHasRoleRequestQuery = { + role: string, account: string +} + +type erc1155ItemsHasRoleRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsHasRoleResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsHasRoleSchema = { + tags: ['erc1155Items'], + querystring: { + type: 'object', + required: ['role', 'account'], + properties: { + role: { type: 'string' }, + account: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsHasRole(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsHasRoleRequestParams + Querystring: erc1155ItemsHasRoleRequestQuery + Reply: erc1155ItemsHasRoleResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/hasRole', + { schema: erc1155ItemsHasRoleSchema }, + async (request, reply) => { + const { role, account } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.hasRole(role, account) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/isApprovedForAll.ts b/src/routes/contract/extensions/erc1155Items/read/isApprovedForAll.ts new file mode 100644 index 0000000..d2bba92 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/isApprovedForAll.ts @@ -0,0 +1,121 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsIsApprovedForAllRequestQuery = { + owner: string, operator: string +} + +type erc1155ItemsIsApprovedForAllRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsIsApprovedForAllResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsIsApprovedForAllSchema = { + tags: ['erc1155Items'], + querystring: { + type: 'object', + required: ['owner', 'operator'], + properties: { + owner: { type: 'string' }, + operator: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsIsApprovedForAll(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsIsApprovedForAllRequestParams + Querystring: erc1155ItemsIsApprovedForAllRequestQuery + Reply: erc1155ItemsIsApprovedForAllResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/isApprovedForAll', + { schema: erc1155ItemsIsApprovedForAllSchema }, + async (request, reply) => { + const { owner, operator } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.isApprovedForAll(owner, operator) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/name.ts b/src/routes/contract/extensions/erc1155Items/read/name.ts new file mode 100644 index 0000000..1f1d992 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/name.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsNameRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsNameResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsNameSchema = { + tags: ['erc1155Items'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsName(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsNameRequestParams + + Reply: erc1155ItemsNameResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/name', + { schema: erc1155ItemsNameSchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.name() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/royaltyInfo.ts b/src/routes/contract/extensions/erc1155Items/read/royaltyInfo.ts new file mode 100644 index 0000000..23b469e --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/royaltyInfo.ts @@ -0,0 +1,121 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsRoyaltyInfoRequestQuery = { + tokenId: string, salePrice: string +} + +type erc1155ItemsRoyaltyInfoRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsRoyaltyInfoResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsRoyaltyInfoSchema = { + tags: ['erc1155Items'], + querystring: { + type: 'object', + required: ['tokenId', 'salePrice'], + properties: { + tokenId: { type: 'string' }, + salePrice: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsRoyaltyInfo(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsRoyaltyInfoRequestParams + Querystring: erc1155ItemsRoyaltyInfoRequestQuery + Reply: erc1155ItemsRoyaltyInfoResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/royaltyInfo', + { schema: erc1155ItemsRoyaltyInfoSchema }, + async (request, reply) => { + const { tokenId, salePrice } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.royaltyInfo(tokenId, salePrice) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/supportsInterface.ts b/src/routes/contract/extensions/erc1155Items/read/supportsInterface.ts new file mode 100644 index 0000000..3479588 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/supportsInterface.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsSupportsInterfaceRequestQuery = { + interfaceId: string +} + +type erc1155ItemsSupportsInterfaceRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsSupportsInterfaceResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsSupportsInterfaceSchema = { + tags: ['erc1155Items'], + querystring: { + type: 'object', + required: ['interfaceId'], + properties: { + interfaceId: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsSupportsInterface(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsSupportsInterfaceRequestParams + Querystring: erc1155ItemsSupportsInterfaceRequestQuery + Reply: erc1155ItemsSupportsInterfaceResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/supportsInterface', + { schema: erc1155ItemsSupportsInterfaceSchema }, + async (request, reply) => { + const { interfaceId } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.supportsInterface(interfaceId) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/tokenSupply.ts b/src/routes/contract/extensions/erc1155Items/read/tokenSupply.ts new file mode 100644 index 0000000..0d6b236 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/tokenSupply.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsTokenSupplyRequestQuery = { + param: string +} + +type erc1155ItemsTokenSupplyRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsTokenSupplyResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsTokenSupplySchema = { + tags: ['erc1155Items'], + querystring: { + type: 'object', + required: ['param'], + properties: { + param: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsTokenSupply(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsTokenSupplyRequestParams + Querystring: erc1155ItemsTokenSupplyRequestQuery + Reply: erc1155ItemsTokenSupplyResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/tokenSupply', + { schema: erc1155ItemsTokenSupplySchema }, + async (request, reply) => { + const { param } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.tokenSupply(param) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/totalSupply.ts b/src/routes/contract/extensions/erc1155Items/read/totalSupply.ts new file mode 100644 index 0000000..18a2d1e --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/totalSupply.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsTotalSupplyRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsTotalSupplyResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsTotalSupplySchema = { + tags: ['erc1155Items'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsTotalSupply(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsTotalSupplyRequestParams + + Reply: erc1155ItemsTotalSupplyResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/totalSupply', + { schema: erc1155ItemsTotalSupplySchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.totalSupply() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/read/uri.ts b/src/routes/contract/extensions/erc1155Items/read/uri.ts new file mode 100644 index 0000000..f5b7669 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/read/uri.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc1155ItemsUriRequestQuery = { + _id: string +} + +type erc1155ItemsUriRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsUriResponse = { + result?: { + data: any + error?: string + } +} + +const erc1155ItemsUriSchema = { + tags: ['erc1155Items'], + querystring: { + type: 'object', + required: ['_id'], + properties: { + _id: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc1155ItemsUri(fastify: FastifyInstance) { + fastify.get<{ + Params: erc1155ItemsUriRequestParams + Querystring: erc1155ItemsUriRequestQuery + Reply: erc1155ItemsUriResponse + }>( + '/read/erc1155Items/:chainId/:contractAddress/uri', + { schema: erc1155ItemsUriSchema }, + async (request, reply) => { + const { _id } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc1155ItemsAbi, + signer + ) + + const result = await contract.uri(_id) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/batchBurn.ts b/src/routes/contract/extensions/erc1155Items/write/batchBurn.ts new file mode 100644 index 0000000..db12e21 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/batchBurn.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsBatchBurnRequestBody = { + tokenIds: string[], amounts: string[], + waitForReceipt?: boolean +} + +type erc1155ItemsBatchBurnRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsBatchBurnResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsBatchBurnSchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['tokenIds', 'amounts'], + properties: { + tokenIds: { type: 'array', items: { type: 'string' } }, + amounts: { type: 'array', items: { type: 'string' } }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsBatchBurn(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsBatchBurnRequestParams + Body: erc1155ItemsBatchBurnRequestBody + Reply: erc1155ItemsBatchBurnResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/batchBurn', + { schema: erc1155ItemsBatchBurnSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { tokenIds, amounts, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('batchBurn', [ + tokenIds.map((v) => BigInt(v)), amounts.map((v) => BigInt(v)) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending batchBurn transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'BatchBurn transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [tokenIds.map(String), amounts.map(String)], + functionName: 'batchBurn' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/batchMint.ts b/src/routes/contract/extensions/erc1155Items/write/batchMint.ts new file mode 100644 index 0000000..372269c --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/batchMint.ts @@ -0,0 +1,190 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsBatchMintRequestBody = { + to: string, tokenIds: string[], amounts: string[], data: string, + waitForReceipt?: boolean +} + +type erc1155ItemsBatchMintRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsBatchMintResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsBatchMintSchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['to', 'tokenIds', 'amounts', 'data'], + properties: { + to: { type: 'string' }, + tokenIds: { type: 'array', items: { type: 'string' } }, + amounts: { type: 'array', items: { type: 'string' } }, + data: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsBatchMint(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsBatchMintRequestParams + Body: erc1155ItemsBatchMintRequestBody + Reply: erc1155ItemsBatchMintResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/batchMint', + { schema: erc1155ItemsBatchMintSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { to, tokenIds, amounts, data, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('batchMint', [ + to, tokenIds.map((v) => BigInt(v)), amounts.map((v) => BigInt(v)), data + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending batchMint transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'BatchMint transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [to, tokenIds.map(String), amounts.map(String), data], + functionName: 'batchMint' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/burn.ts b/src/routes/contract/extensions/erc1155Items/write/burn.ts new file mode 100644 index 0000000..6b14f0b --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/burn.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsBurnRequestBody = { + tokenId: string, amount: string, + waitForReceipt?: boolean +} + +type erc1155ItemsBurnRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsBurnResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsBurnSchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['tokenId', 'amount'], + properties: { + tokenId: { type: 'string' }, + amount: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsBurn(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsBurnRequestParams + Body: erc1155ItemsBurnRequestBody + Reply: erc1155ItemsBurnResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/burn', + { schema: erc1155ItemsBurnSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { tokenId, amount, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('burn', [ + BigInt(tokenId), BigInt(amount) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending burn transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'Burn transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [String(tokenId), String(amount)], + functionName: 'burn' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/grantRole.ts b/src/routes/contract/extensions/erc1155Items/write/grantRole.ts new file mode 100644 index 0000000..440262f --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/grantRole.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsGrantRoleRequestBody = { + role: string, account: string, + waitForReceipt?: boolean +} + +type erc1155ItemsGrantRoleRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsGrantRoleResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsGrantRoleSchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['role', 'account'], + properties: { + role: { type: 'string' }, + account: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsGrantRole(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsGrantRoleRequestParams + Body: erc1155ItemsGrantRoleRequestBody + Reply: erc1155ItemsGrantRoleResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/grantRole', + { schema: erc1155ItemsGrantRoleSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { role, account, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('grantRole', [ + role, account + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending grantRole transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'GrantRole transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [role, account], + functionName: 'grantRole' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/initialize.ts b/src/routes/contract/extensions/erc1155Items/write/initialize.ts new file mode 100644 index 0000000..a944063 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/initialize.ts @@ -0,0 +1,194 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsInitializeRequestBody = { + owner: string, tokenName: string, tokenBaseURI: string, tokenContractURI: string, royaltyReceiver: string, royaltyFeeNumerator: string, implicitModeValidator: string, implicitModeProjectId: string, + waitForReceipt?: boolean +} + +type erc1155ItemsInitializeRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsInitializeResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsInitializeSchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['owner', 'tokenName', 'tokenBaseURI', 'tokenContractURI', 'royaltyReceiver', 'royaltyFeeNumerator', 'implicitModeValidator', 'implicitModeProjectId'], + properties: { + owner: { type: 'string' }, + tokenName: { type: 'string' }, + tokenBaseURI: { type: 'string' }, + tokenContractURI: { type: 'string' }, + royaltyReceiver: { type: 'string' }, + royaltyFeeNumerator: { type: 'string' }, + implicitModeValidator: { type: 'string' }, + implicitModeProjectId: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsInitialize(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsInitializeRequestParams + Body: erc1155ItemsInitializeRequestBody + Reply: erc1155ItemsInitializeResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/initialize', + { schema: erc1155ItemsInitializeSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { owner, tokenName, tokenBaseURI, tokenContractURI, royaltyReceiver, royaltyFeeNumerator, implicitModeValidator, implicitModeProjectId, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('initialize', [ + owner, tokenName, tokenBaseURI, tokenContractURI, royaltyReceiver, BigInt(royaltyFeeNumerator), implicitModeValidator, implicitModeProjectId + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending initialize transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'Initialize transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [owner, tokenName, tokenBaseURI, tokenContractURI, royaltyReceiver, String(royaltyFeeNumerator), implicitModeValidator, implicitModeProjectId], + functionName: 'initialize' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/mint.ts b/src/routes/contract/extensions/erc1155Items/write/mint.ts new file mode 100644 index 0000000..0edb7db --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/mint.ts @@ -0,0 +1,190 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsMintRequestBody = { + to: string, tokenId: string, amount: string, data: string, + waitForReceipt?: boolean +} + +type erc1155ItemsMintRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsMintResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsMintSchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['to', 'tokenId', 'amount', 'data'], + properties: { + to: { type: 'string' }, + tokenId: { type: 'string' }, + amount: { type: 'string' }, + data: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsMint(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsMintRequestParams + Body: erc1155ItemsMintRequestBody + Reply: erc1155ItemsMintResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/mint', + { schema: erc1155ItemsMintSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { to, tokenId, amount, data, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('mint', [ + to, BigInt(tokenId), BigInt(amount), data + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending mint transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'Mint transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [to, String(tokenId), String(amount), data], + functionName: 'mint' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/renounceRole.ts b/src/routes/contract/extensions/erc1155Items/write/renounceRole.ts new file mode 100644 index 0000000..5e044e2 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/renounceRole.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsRenounceRoleRequestBody = { + role: string, account: string, + waitForReceipt?: boolean +} + +type erc1155ItemsRenounceRoleRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsRenounceRoleResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsRenounceRoleSchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['role', 'account'], + properties: { + role: { type: 'string' }, + account: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsRenounceRole(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsRenounceRoleRequestParams + Body: erc1155ItemsRenounceRoleRequestBody + Reply: erc1155ItemsRenounceRoleResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/renounceRole', + { schema: erc1155ItemsRenounceRoleSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { role, account, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('renounceRole', [ + role, account + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending renounceRole transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'RenounceRole transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [role, account], + functionName: 'renounceRole' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/revokeRole.ts b/src/routes/contract/extensions/erc1155Items/write/revokeRole.ts new file mode 100644 index 0000000..ee30b6e --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/revokeRole.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsRevokeRoleRequestBody = { + role: string, account: string, + waitForReceipt?: boolean +} + +type erc1155ItemsRevokeRoleRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsRevokeRoleResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsRevokeRoleSchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['role', 'account'], + properties: { + role: { type: 'string' }, + account: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsRevokeRole(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsRevokeRoleRequestParams + Body: erc1155ItemsRevokeRoleRequestBody + Reply: erc1155ItemsRevokeRoleResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/revokeRole', + { schema: erc1155ItemsRevokeRoleSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { role, account, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('revokeRole', [ + role, account + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending revokeRole transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'RevokeRole transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [role, account], + functionName: 'revokeRole' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/safeBatchTransferFrom.ts b/src/routes/contract/extensions/erc1155Items/write/safeBatchTransferFrom.ts new file mode 100644 index 0000000..63e2794 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/safeBatchTransferFrom.ts @@ -0,0 +1,191 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsSafeBatchTransferFromRequestBody = { + from: string, to: string, ids: string[], amounts: string[], data: string, + waitForReceipt?: boolean +} + +type erc1155ItemsSafeBatchTransferFromRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsSafeBatchTransferFromResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsSafeBatchTransferFromSchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['from', 'to', 'ids', 'amounts', 'data'], + properties: { + from: { type: 'string' }, + to: { type: 'string' }, + ids: { type: 'array', items: { type: 'string' } }, + amounts: { type: 'array', items: { type: 'string' } }, + data: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsSafeBatchTransferFrom(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsSafeBatchTransferFromRequestParams + Body: erc1155ItemsSafeBatchTransferFromRequestBody + Reply: erc1155ItemsSafeBatchTransferFromResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/safeBatchTransferFrom', + { schema: erc1155ItemsSafeBatchTransferFromSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { from, to, ids, amounts, data, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('safeBatchTransferFrom', [ + from, to, ids.map((v) => BigInt(v)), amounts.map((v) => BigInt(v)), data + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending safeBatchTransferFrom transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SafeBatchTransferFrom transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [from, to, ids.map(String), amounts.map(String), data], + functionName: 'safeBatchTransferFrom' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/safeTransferFrom.ts b/src/routes/contract/extensions/erc1155Items/write/safeTransferFrom.ts new file mode 100644 index 0000000..b3ec11a --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/safeTransferFrom.ts @@ -0,0 +1,191 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsSafeTransferFromRequestBody = { + from: string, to: string, id: string, amount: string, data: string, + waitForReceipt?: boolean +} + +type erc1155ItemsSafeTransferFromRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsSafeTransferFromResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsSafeTransferFromSchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['from', 'to', 'id', 'amount', 'data'], + properties: { + from: { type: 'string' }, + to: { type: 'string' }, + id: { type: 'string' }, + amount: { type: 'string' }, + data: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsSafeTransferFrom(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsSafeTransferFromRequestParams + Body: erc1155ItemsSafeTransferFromRequestBody + Reply: erc1155ItemsSafeTransferFromResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/safeTransferFrom', + { schema: erc1155ItemsSafeTransferFromSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { from, to, id, amount, data, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('safeTransferFrom', [ + from, to, BigInt(id), BigInt(amount), data + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending safeTransferFrom transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SafeTransferFrom transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [from, to, String(id), String(amount), data], + functionName: 'safeTransferFrom' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/setApprovalForAll.ts b/src/routes/contract/extensions/erc1155Items/write/setApprovalForAll.ts new file mode 100644 index 0000000..834d253 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/setApprovalForAll.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsSetApprovalForAllRequestBody = { + operator: string, isApproved: string, + waitForReceipt?: boolean +} + +type erc1155ItemsSetApprovalForAllRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsSetApprovalForAllResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsSetApprovalForAllSchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['operator', 'isApproved'], + properties: { + operator: { type: 'string' }, + isApproved: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsSetApprovalForAll(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsSetApprovalForAllRequestParams + Body: erc1155ItemsSetApprovalForAllRequestBody + Reply: erc1155ItemsSetApprovalForAllResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/setApprovalForAll', + { schema: erc1155ItemsSetApprovalForAllSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { operator, isApproved, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setApprovalForAll', [ + operator, isApproved + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setApprovalForAll transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetApprovalForAll transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [operator, isApproved], + functionName: 'setApprovalForAll' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/setBaseMetadataURI.ts b/src/routes/contract/extensions/erc1155Items/write/setBaseMetadataURI.ts new file mode 100644 index 0000000..512a4c7 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/setBaseMetadataURI.ts @@ -0,0 +1,187 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsSetBaseMetadataURIRequestBody = { + tokenBaseURI: string, + waitForReceipt?: boolean +} + +type erc1155ItemsSetBaseMetadataURIRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsSetBaseMetadataURIResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsSetBaseMetadataURISchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['tokenBaseURI'], + properties: { + tokenBaseURI: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsSetBaseMetadataURI(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsSetBaseMetadataURIRequestParams + Body: erc1155ItemsSetBaseMetadataURIRequestBody + Reply: erc1155ItemsSetBaseMetadataURIResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/setBaseMetadataURI', + { schema: erc1155ItemsSetBaseMetadataURISchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { tokenBaseURI, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setBaseMetadataURI', [ + tokenBaseURI + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setBaseMetadataURI transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetBaseMetadataURI transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [tokenBaseURI], + functionName: 'setBaseMetadataURI' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/setContractName.ts b/src/routes/contract/extensions/erc1155Items/write/setContractName.ts new file mode 100644 index 0000000..810505e --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/setContractName.ts @@ -0,0 +1,187 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsSetContractNameRequestBody = { + tokenName: string, + waitForReceipt?: boolean +} + +type erc1155ItemsSetContractNameRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsSetContractNameResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsSetContractNameSchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['tokenName'], + properties: { + tokenName: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsSetContractName(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsSetContractNameRequestParams + Body: erc1155ItemsSetContractNameRequestBody + Reply: erc1155ItemsSetContractNameResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/setContractName', + { schema: erc1155ItemsSetContractNameSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { tokenName, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setContractName', [ + tokenName + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setContractName transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetContractName transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [tokenName], + functionName: 'setContractName' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/setContractURI.ts b/src/routes/contract/extensions/erc1155Items/write/setContractURI.ts new file mode 100644 index 0000000..5eff575 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/setContractURI.ts @@ -0,0 +1,187 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsSetContractURIRequestBody = { + tokenContractURI: string, + waitForReceipt?: boolean +} + +type erc1155ItemsSetContractURIRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsSetContractURIResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsSetContractURISchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['tokenContractURI'], + properties: { + tokenContractURI: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsSetContractURI(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsSetContractURIRequestParams + Body: erc1155ItemsSetContractURIRequestBody + Reply: erc1155ItemsSetContractURIResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/setContractURI', + { schema: erc1155ItemsSetContractURISchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { tokenContractURI, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setContractURI', [ + tokenContractURI + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setContractURI transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetContractURI transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [tokenContractURI], + functionName: 'setContractURI' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/setDefaultRoyalty.ts b/src/routes/contract/extensions/erc1155Items/write/setDefaultRoyalty.ts new file mode 100644 index 0000000..c80ff7b --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/setDefaultRoyalty.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsSetDefaultRoyaltyRequestBody = { + receiver: string, feeNumerator: string, + waitForReceipt?: boolean +} + +type erc1155ItemsSetDefaultRoyaltyRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsSetDefaultRoyaltyResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsSetDefaultRoyaltySchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['receiver', 'feeNumerator'], + properties: { + receiver: { type: 'string' }, + feeNumerator: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsSetDefaultRoyalty(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsSetDefaultRoyaltyRequestParams + Body: erc1155ItemsSetDefaultRoyaltyRequestBody + Reply: erc1155ItemsSetDefaultRoyaltyResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/setDefaultRoyalty', + { schema: erc1155ItemsSetDefaultRoyaltySchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { receiver, feeNumerator, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setDefaultRoyalty', [ + receiver, BigInt(feeNumerator) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setDefaultRoyalty transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetDefaultRoyalty transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [receiver, String(feeNumerator)], + functionName: 'setDefaultRoyalty' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/setImplicitModeProjectId.ts b/src/routes/contract/extensions/erc1155Items/write/setImplicitModeProjectId.ts new file mode 100644 index 0000000..759469e --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/setImplicitModeProjectId.ts @@ -0,0 +1,187 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsSetImplicitModeProjectIdRequestBody = { + projectId: string, + waitForReceipt?: boolean +} + +type erc1155ItemsSetImplicitModeProjectIdRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsSetImplicitModeProjectIdResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsSetImplicitModeProjectIdSchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['projectId'], + properties: { + projectId: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsSetImplicitModeProjectId(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsSetImplicitModeProjectIdRequestParams + Body: erc1155ItemsSetImplicitModeProjectIdRequestBody + Reply: erc1155ItemsSetImplicitModeProjectIdResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/setImplicitModeProjectId', + { schema: erc1155ItemsSetImplicitModeProjectIdSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { projectId, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setImplicitModeProjectId', [ + projectId + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setImplicitModeProjectId transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetImplicitModeProjectId transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [projectId], + functionName: 'setImplicitModeProjectId' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/setImplicitModeValidator.ts b/src/routes/contract/extensions/erc1155Items/write/setImplicitModeValidator.ts new file mode 100644 index 0000000..4b62db8 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/setImplicitModeValidator.ts @@ -0,0 +1,187 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsSetImplicitModeValidatorRequestBody = { + validator: string, + waitForReceipt?: boolean +} + +type erc1155ItemsSetImplicitModeValidatorRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsSetImplicitModeValidatorResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsSetImplicitModeValidatorSchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['validator'], + properties: { + validator: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsSetImplicitModeValidator(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsSetImplicitModeValidatorRequestParams + Body: erc1155ItemsSetImplicitModeValidatorRequestBody + Reply: erc1155ItemsSetImplicitModeValidatorResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/setImplicitModeValidator', + { schema: erc1155ItemsSetImplicitModeValidatorSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { validator, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setImplicitModeValidator', [ + validator + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setImplicitModeValidator transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetImplicitModeValidator transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [validator], + functionName: 'setImplicitModeValidator' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc1155Items/write/setTokenRoyalty.ts b/src/routes/contract/extensions/erc1155Items/write/setTokenRoyalty.ts new file mode 100644 index 0000000..ad017f3 --- /dev/null +++ b/src/routes/contract/extensions/erc1155Items/write/setTokenRoyalty.ts @@ -0,0 +1,189 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc1155ItemsAbi } from '~/constants/abis/erc1155Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc1155ItemsSetTokenRoyaltyRequestBody = { + tokenId: string, receiver: string, feeNumerator: string, + waitForReceipt?: boolean +} + +type erc1155ItemsSetTokenRoyaltyRequestParams = { + chainId: string + contractAddress: string +} + +type erc1155ItemsSetTokenRoyaltyResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc1155ItemsSetTokenRoyaltySchema = { + tags: ['erc1155Items'], + body: { + type: 'object', + required: ['tokenId', 'receiver', 'feeNumerator'], + properties: { + tokenId: { type: 'string' }, + receiver: { type: 'string' }, + feeNumerator: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc1155ItemsSetTokenRoyalty(fastify: FastifyInstance) { + fastify.post<{ + Params: erc1155ItemsSetTokenRoyaltyRequestParams + Body: erc1155ItemsSetTokenRoyaltyRequestBody + Reply: erc1155ItemsSetTokenRoyaltyResponse + }>( + '/write/erc1155Items/:chainId/:contractAddress/setTokenRoyalty', + { schema: erc1155ItemsSetTokenRoyaltySchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { tokenId, receiver, feeNumerator, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc1155ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setTokenRoyalty', [ + BigInt(tokenId), receiver, BigInt(feeNumerator) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setTokenRoyalty transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetTokenRoyalty transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc1155ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [String(tokenId), receiver, String(feeNumerator)], + functionName: 'setTokenRoyalty' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/index.ts b/src/routes/contract/extensions/erc20/index.ts new file mode 100644 index 0000000..bad3d18 --- /dev/null +++ b/src/routes/contract/extensions/erc20/index.ts @@ -0,0 +1,43 @@ +import type { FastifyInstance } from 'fastify' + +import { erc20Approve } from './write/approve' +import { erc20Burn } from './write/burn' +import { erc20BurnFrom } from './write/burnFrom' +import { erc20Mint } from './write/mint' +import { erc20Permit } from './write/permit' +import { erc20RenounceOwnership } from './write/renounceOwnership' +import { erc20Transfer } from './write/transfer' +import { erc20TransferFrom } from './write/transferFrom' +import { erc20TransferOwnership } from './write/transferOwnership' +import { erc20DOMAIN_SEPARATOR } from './read/DOMAIN_SEPARATOR' +import { erc20Allowance } from './read/allowance' +import { erc20BalanceOf } from './read/balanceOf' +import { erc20Decimals } from './read/decimals' +import { erc20Eip712Domain } from './read/eip712Domain' +import { erc20Name } from './read/name' +import { erc20Nonces } from './read/nonces' +import { erc20Owner } from './read/owner' +import { erc20Symbol } from './read/symbol' +import { erc20TotalSupply } from './read/totalSupply' + +export function registerErc20Routes(fastify: FastifyInstance) { + erc20Approve(fastify) + erc20Burn(fastify) + erc20BurnFrom(fastify) + erc20Mint(fastify) + erc20Permit(fastify) + erc20RenounceOwnership(fastify) + erc20Transfer(fastify) + erc20TransferFrom(fastify) + erc20TransferOwnership(fastify) + erc20DOMAIN_SEPARATOR(fastify) + erc20Allowance(fastify) + erc20BalanceOf(fastify) + erc20Decimals(fastify) + erc20Eip712Domain(fastify) + erc20Name(fastify) + erc20Nonces(fastify) + erc20Owner(fastify) + erc20Symbol(fastify) + erc20TotalSupply(fastify) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/read/DOMAIN_SEPARATOR.ts b/src/routes/contract/extensions/erc20/read/DOMAIN_SEPARATOR.ts new file mode 100644 index 0000000..2cc166b --- /dev/null +++ b/src/routes/contract/extensions/erc20/read/DOMAIN_SEPARATOR.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc20Abi } from '~/constants/abis/erc20' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc20DOMAIN_SEPARATORRequestParams = { + chainId: string + contractAddress: string +} + +type erc20DOMAIN_SEPARATORResponse = { + result?: { + data: any + error?: string + } +} + +const erc20DOMAIN_SEPARATORSchema = { + tags: ['erc20'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc20DOMAIN_SEPARATOR(fastify: FastifyInstance) { + fastify.get<{ + Params: erc20DOMAIN_SEPARATORRequestParams + + Reply: erc20DOMAIN_SEPARATORResponse + }>( + '/read/erc20/:chainId/:contractAddress/DOMAIN_SEPARATOR', + { schema: erc20DOMAIN_SEPARATORSchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc20Abi, + signer + ) + + const result = await contract.DOMAIN_SEPARATOR() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/read/allowance.ts b/src/routes/contract/extensions/erc20/read/allowance.ts new file mode 100644 index 0000000..ad842cd --- /dev/null +++ b/src/routes/contract/extensions/erc20/read/allowance.ts @@ -0,0 +1,121 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc20Abi } from '~/constants/abis/erc20' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc20AllowanceRequestQuery = { + owner: string, spender: string +} + +type erc20AllowanceRequestParams = { + chainId: string + contractAddress: string +} + +type erc20AllowanceResponse = { + result?: { + data: any + error?: string + } +} + +const erc20AllowanceSchema = { + tags: ['erc20'], + querystring: { + type: 'object', + required: ['owner', 'spender'], + properties: { + owner: { type: 'string' }, + spender: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc20Allowance(fastify: FastifyInstance) { + fastify.get<{ + Params: erc20AllowanceRequestParams + Querystring: erc20AllowanceRequestQuery + Reply: erc20AllowanceResponse + }>( + '/read/erc20/:chainId/:contractAddress/allowance', + { schema: erc20AllowanceSchema }, + async (request, reply) => { + const { owner, spender } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc20Abi, + signer + ) + + const result = await contract.allowance(owner, spender) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/read/balanceOf.ts b/src/routes/contract/extensions/erc20/read/balanceOf.ts new file mode 100644 index 0000000..bd6188b --- /dev/null +++ b/src/routes/contract/extensions/erc20/read/balanceOf.ts @@ -0,0 +1,123 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc20Abi } from '~/constants/abis/erc20' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + console.log('serializeBigInt', obj) + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc20BalanceOfRequestQuery = { + account: string +} + +type erc20BalanceOfRequestParams = { + chainId: string + contractAddress: string +} + +type erc20BalanceOfResponse = { + result?: { + data: any + error?: string + } +} + +const erc20BalanceOfSchema = { + tags: ['erc20'], + querystring: { + type: 'object', + required: ['account'], + properties: { + account: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc20BalanceOf(fastify: FastifyInstance) { + fastify.get<{ + Params: erc20BalanceOfRequestParams + Querystring: erc20BalanceOfRequestQuery + Reply: erc20BalanceOfResponse + }>( + '/read/erc20/:chainId/:contractAddress/balanceOf', + { schema: erc20BalanceOfSchema }, + async (request, reply) => { + const { account } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc20Abi, + signer + ) + + const result = await contract.balanceOf(account) + + console.log('result', result) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/read/decimals.ts b/src/routes/contract/extensions/erc20/read/decimals.ts new file mode 100644 index 0000000..09593af --- /dev/null +++ b/src/routes/contract/extensions/erc20/read/decimals.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc20Abi } from '~/constants/abis/erc20' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc20DecimalsRequestParams = { + chainId: string + contractAddress: string +} + +type erc20DecimalsResponse = { + result?: { + data: any + error?: string + } +} + +const erc20DecimalsSchema = { + tags: ['erc20'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc20Decimals(fastify: FastifyInstance) { + fastify.get<{ + Params: erc20DecimalsRequestParams + + Reply: erc20DecimalsResponse + }>( + '/read/erc20/:chainId/:contractAddress/decimals', + { schema: erc20DecimalsSchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc20Abi, + signer + ) + + const result = await contract.decimals() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/read/eip712Domain.ts b/src/routes/contract/extensions/erc20/read/eip712Domain.ts new file mode 100644 index 0000000..dbb42cd --- /dev/null +++ b/src/routes/contract/extensions/erc20/read/eip712Domain.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc20Abi } from '~/constants/abis/erc20' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc20Eip712DomainRequestParams = { + chainId: string + contractAddress: string +} + +type erc20Eip712DomainResponse = { + result?: { + data: any + error?: string + } +} + +const erc20Eip712DomainSchema = { + tags: ['erc20'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc20Eip712Domain(fastify: FastifyInstance) { + fastify.get<{ + Params: erc20Eip712DomainRequestParams + + Reply: erc20Eip712DomainResponse + }>( + '/read/erc20/:chainId/:contractAddress/eip712Domain', + { schema: erc20Eip712DomainSchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc20Abi, + signer + ) + + const result = await contract.eip712Domain() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/read/name.ts b/src/routes/contract/extensions/erc20/read/name.ts new file mode 100644 index 0000000..80ea022 --- /dev/null +++ b/src/routes/contract/extensions/erc20/read/name.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc20Abi } from '~/constants/abis/erc20' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc20NameRequestParams = { + chainId: string + contractAddress: string +} + +type erc20NameResponse = { + result?: { + data: any + error?: string + } +} + +const erc20NameSchema = { + tags: ['erc20'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc20Name(fastify: FastifyInstance) { + fastify.get<{ + Params: erc20NameRequestParams + + Reply: erc20NameResponse + }>( + '/read/erc20/:chainId/:contractAddress/name', + { schema: erc20NameSchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc20Abi, + signer + ) + + const result = await contract.name() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/read/nonces.ts b/src/routes/contract/extensions/erc20/read/nonces.ts new file mode 100644 index 0000000..b74d328 --- /dev/null +++ b/src/routes/contract/extensions/erc20/read/nonces.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc20Abi } from '~/constants/abis/erc20' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc20NoncesRequestQuery = { + owner: string +} + +type erc20NoncesRequestParams = { + chainId: string + contractAddress: string +} + +type erc20NoncesResponse = { + result?: { + data: any + error?: string + } +} + +const erc20NoncesSchema = { + tags: ['erc20'], + querystring: { + type: 'object', + required: ['owner'], + properties: { + owner: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc20Nonces(fastify: FastifyInstance) { + fastify.get<{ + Params: erc20NoncesRequestParams + Querystring: erc20NoncesRequestQuery + Reply: erc20NoncesResponse + }>( + '/read/erc20/:chainId/:contractAddress/nonces', + { schema: erc20NoncesSchema }, + async (request, reply) => { + const { owner } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc20Abi, + signer + ) + + const result = await contract.nonces(owner) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/read/owner.ts b/src/routes/contract/extensions/erc20/read/owner.ts new file mode 100644 index 0000000..1678874 --- /dev/null +++ b/src/routes/contract/extensions/erc20/read/owner.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc20Abi } from '~/constants/abis/erc20' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc20OwnerRequestParams = { + chainId: string + contractAddress: string +} + +type erc20OwnerResponse = { + result?: { + data: any + error?: string + } +} + +const erc20OwnerSchema = { + tags: ['erc20'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc20Owner(fastify: FastifyInstance) { + fastify.get<{ + Params: erc20OwnerRequestParams + + Reply: erc20OwnerResponse + }>( + '/read/erc20/:chainId/:contractAddress/owner', + { schema: erc20OwnerSchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc20Abi, + signer + ) + + const result = await contract.owner() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/read/symbol.ts b/src/routes/contract/extensions/erc20/read/symbol.ts new file mode 100644 index 0000000..2913cb8 --- /dev/null +++ b/src/routes/contract/extensions/erc20/read/symbol.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc20Abi } from '~/constants/abis/erc20' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc20SymbolRequestParams = { + chainId: string + contractAddress: string +} + +type erc20SymbolResponse = { + result?: { + data: any + error?: string + } +} + +const erc20SymbolSchema = { + tags: ['erc20'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc20Symbol(fastify: FastifyInstance) { + fastify.get<{ + Params: erc20SymbolRequestParams + + Reply: erc20SymbolResponse + }>( + '/read/erc20/:chainId/:contractAddress/symbol', + { schema: erc20SymbolSchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc20Abi, + signer + ) + + const result = await contract.symbol() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/read/totalSupply.ts b/src/routes/contract/extensions/erc20/read/totalSupply.ts new file mode 100644 index 0000000..398f4ff --- /dev/null +++ b/src/routes/contract/extensions/erc20/read/totalSupply.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc20Abi } from '~/constants/abis/erc20' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc20TotalSupplyRequestParams = { + chainId: string + contractAddress: string +} + +type erc20TotalSupplyResponse = { + result?: { + data: any + error?: string + } +} + +const erc20TotalSupplySchema = { + tags: ['erc20'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc20TotalSupply(fastify: FastifyInstance) { + fastify.get<{ + Params: erc20TotalSupplyRequestParams + + Reply: erc20TotalSupplyResponse + }>( + '/read/erc20/:chainId/:contractAddress/totalSupply', + { schema: erc20TotalSupplySchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc20Abi, + signer + ) + + const result = await contract.totalSupply() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/write/approve.ts b/src/routes/contract/extensions/erc20/write/approve.ts index 5c1ad06..d0a3f47 100644 --- a/src/routes/contract/extensions/erc20/write/approve.ts +++ b/src/routes/contract/extensions/erc20/write/approve.ts @@ -1,201 +1,188 @@ import type { FastifyInstance } from 'fastify' - import { ethers } from 'ethers' import { erc20Abi } from '~/constants/abis/erc20' import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation } from '~/routes/contract/utils/tenderly/getSimulationUrl' import { TransactionService } from '~/services/transaction.service' import type { TransactionResponse } from '~/types/general' import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' import { getSigner } from '~/utils/wallet' -type ERC20ApproveRequestBody = { - spender: string - amount: string - waitForReceipt?: boolean +type erc20ApproveRequestBody = { + spender: string, value: string, + waitForReceipt?: boolean } -type ERC20ApproveRequestParams = { - chainId: string - contractAddress: string +type erc20ApproveRequestParams = { + chainId: string + contractAddress: string } -type ERC20ApproveResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } +type erc20ApproveResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } } -const ERC20ApproveSchema = { - tags: ['ERC20'], - body: { - type: 'object', - required: ['spender', 'amount'], - properties: { - spender: { type: 'string' }, - amount: { type: 'string' }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - } - } +const erc20ApproveSchema = { + tags: ['erc20'], + body: { + type: 'object', + required: ['spender', 'value'], + properties: { + spender: { type: 'string' }, + value: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } } export async function erc20Approve(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC20ApproveRequestParams - Body: ERC20ApproveRequestBody - Reply: ERC20ApproveResponse - }>( - '/write/erc20/:chainId/:contractAddress/approve', - { - schema: ERC20ApproveSchema - }, - async (request, reply) => { - logRequest(request) - - let tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { spender, amount, waitForReceipt } = request.body - - const signer = await getSigner(chainId) - if (!signer || !signer.account?.address) { - logError(request, new Error('Signer not configured correctly.'), { - signer - }) - throw new Error('Signer not configured correctly.') - } - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - - const contract = new ethers.Contract(contractAddress, erc20Abi, signer) - logStep(request, 'Contract instance created', { contractAddress }) - - const data = contract.interface.encodeFunctionData('approve', [ - spender, - amount - ]) - logStep(request, 'Function data encoded', { spender, amount }) - - const tx = { - to: contractAddress, - data - } - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - const txService = new TransactionService(fastify) - - logStep(request, 'Sending approve transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - tx, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'Approve transaction sent', { - txHash: txResponse.hash - }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc20Abi, - data: tx.data, - txHash: txHash, - isDeployTx: false, - args: [spender, amount], - functionName: 'approve' - }) - - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error ? error.message : 'Failed to execute approve' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} + fastify.post<{ + Params: erc20ApproveRequestParams + Body: erc20ApproveRequestBody + Reply: erc20ApproveResponse + }>( + '/write/erc20/:chainId/:contractAddress/approve', + { schema: erc20ApproveSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { spender, value, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc20Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('approve', [ + spender, BigInt(value) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending approve transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'Approve transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc20Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [spender, String(value)], + functionName: 'approve' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/write/burn.ts b/src/routes/contract/extensions/erc20/write/burn.ts new file mode 100644 index 0000000..441e77e --- /dev/null +++ b/src/routes/contract/extensions/erc20/write/burn.ts @@ -0,0 +1,187 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc20Abi } from '~/constants/abis/erc20' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc20BurnRequestBody = { + value: string, + waitForReceipt?: boolean +} + +type erc20BurnRequestParams = { + chainId: string + contractAddress: string +} + +type erc20BurnResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc20BurnSchema = { + tags: ['erc20'], + body: { + type: 'object', + required: ['value'], + properties: { + value: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc20Burn(fastify: FastifyInstance) { + fastify.post<{ + Params: erc20BurnRequestParams + Body: erc20BurnRequestBody + Reply: erc20BurnResponse + }>( + '/write/erc20/:chainId/:contractAddress/burn', + { schema: erc20BurnSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { value, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc20Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('burn', [ + BigInt(value) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending burn transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'Burn transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc20Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [String(value)], + functionName: 'burn' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/write/burnFrom.ts b/src/routes/contract/extensions/erc20/write/burnFrom.ts new file mode 100644 index 0000000..0d6d33b --- /dev/null +++ b/src/routes/contract/extensions/erc20/write/burnFrom.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc20Abi } from '~/constants/abis/erc20' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc20BurnFromRequestBody = { + account: string, value: string, + waitForReceipt?: boolean +} + +type erc20BurnFromRequestParams = { + chainId: string + contractAddress: string +} + +type erc20BurnFromResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc20BurnFromSchema = { + tags: ['erc20'], + body: { + type: 'object', + required: ['account', 'value'], + properties: { + account: { type: 'string' }, + value: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc20BurnFrom(fastify: FastifyInstance) { + fastify.post<{ + Params: erc20BurnFromRequestParams + Body: erc20BurnFromRequestBody + Reply: erc20BurnFromResponse + }>( + '/write/erc20/:chainId/:contractAddress/burnFrom', + { schema: erc20BurnFromSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { account, value, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc20Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('burnFrom', [ + account, BigInt(value) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending burnFrom transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'BurnFrom transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc20Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [account, String(value)], + functionName: 'burnFrom' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/write/mint.ts b/src/routes/contract/extensions/erc20/write/mint.ts index 4cd9f07..061121f 100644 --- a/src/routes/contract/extensions/erc20/write/mint.ts +++ b/src/routes/contract/extensions/erc20/write/mint.ts @@ -1,192 +1,188 @@ import type { FastifyInstance } from 'fastify' - import { ethers } from 'ethers' import { erc20Abi } from '~/constants/abis/erc20' import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation } from '~/routes/contract/utils/tenderly/getSimulationUrl' import { TransactionService } from '~/services/transaction.service' import type { TransactionResponse } from '~/types/general' import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' import { getSigner } from '~/utils/wallet' -type ERC20MintRequestBody = { - to: string - amount: string - waitForReceipt?: boolean +type erc20MintRequestBody = { + to: string, amount: string, + waitForReceipt?: boolean } -type ERC20MintRequestParams = { - chainId: string - contractAddress: string +type erc20MintRequestParams = { + chainId: string + contractAddress: string } -type ERC20MintResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } +type erc20MintResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } } -const ERC20MintSchema = { - tags: ['ERC20'], - body: { - type: 'object', - required: ['to', 'amount'], - properties: { - to: { type: 'string' }, - amount: { type: 'string' }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - } - } +const erc20MintSchema = { + tags: ['erc20'], + body: { + type: 'object', + required: ['to', 'amount'], + properties: { + to: { type: 'string' }, + amount: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } } export async function erc20Mint(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC20MintRequestParams - Body: ERC20MintRequestBody - Reply: ERC20MintResponse - }>( - '/write/erc20/:chainId/:contractAddress/mint', - { - schema: ERC20MintSchema - }, - async (request, reply) => { - logRequest(request) - - let tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { to, amount, waitForReceipt } = request.body - - const signer = await getSigner(chainId) - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - - const contract = new ethers.Contract(contractAddress, erc20Abi, signer) - - const data = contract.interface.encodeFunctionData('mint', [to, amount]) - - const tx = { - to: contractAddress, - data - } - logStep(request, 'Tx prepared', { tx }) - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - const txService = new TransactionService(fastify) - - logStep(request, 'Sending mint transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - tx, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'Mint transaction sent', { txHash: txResponse.hash }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc20Abi, - data: tx.data, - txHash: txHash, - isDeployTx: false, - args: [to, amount], - functionName: 'mint' - }) - - logStep(request, 'Mint transaction success', { - txHash: txHash - }) - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error ? error.message : 'Failed to execute mint' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} + fastify.post<{ + Params: erc20MintRequestParams + Body: erc20MintRequestBody + Reply: erc20MintResponse + }>( + '/write/erc20/:chainId/:contractAddress/mint', + { schema: erc20MintSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { to, amount, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc20Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('mint', [ + to, BigInt(amount) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending mint transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'Mint transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc20Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [to, String(amount)], + functionName: 'mint' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/write/permit.ts b/src/routes/contract/extensions/erc20/write/permit.ts new file mode 100644 index 0000000..78a3c1c --- /dev/null +++ b/src/routes/contract/extensions/erc20/write/permit.ts @@ -0,0 +1,193 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc20Abi } from '~/constants/abis/erc20' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc20PermitRequestBody = { + owner: string, spender: string, value: string, deadline: string, v: string, r: string, s: string, + waitForReceipt?: boolean +} + +type erc20PermitRequestParams = { + chainId: string + contractAddress: string +} + +type erc20PermitResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc20PermitSchema = { + tags: ['erc20'], + body: { + type: 'object', + required: ['owner', 'spender', 'value', 'deadline', 'v', 'r', 's'], + properties: { + owner: { type: 'string' }, + spender: { type: 'string' }, + value: { type: 'string' }, + deadline: { type: 'string' }, + v: { type: 'string' }, + r: { type: 'string' }, + s: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc20Permit(fastify: FastifyInstance) { + fastify.post<{ + Params: erc20PermitRequestParams + Body: erc20PermitRequestBody + Reply: erc20PermitResponse + }>( + '/write/erc20/:chainId/:contractAddress/permit', + { schema: erc20PermitSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { owner, spender, value, deadline, v, r, s, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc20Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('permit', [ + owner, spender, BigInt(value), BigInt(deadline), BigInt(v), r, s + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending permit transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'Permit transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc20Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [owner, spender, String(value), String(deadline), String(v), r, s], + functionName: 'permit' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/write/renounceOwnership.ts b/src/routes/contract/extensions/erc20/write/renounceOwnership.ts new file mode 100644 index 0000000..e8db54f --- /dev/null +++ b/src/routes/contract/extensions/erc20/write/renounceOwnership.ts @@ -0,0 +1,186 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc20Abi } from '~/constants/abis/erc20' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc20RenounceOwnershipRequestBody = { + + waitForReceipt?: boolean +} + +type erc20RenounceOwnershipRequestParams = { + chainId: string + contractAddress: string +} + +type erc20RenounceOwnershipResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc20RenounceOwnershipSchema = { + tags: ['erc20'], + body: { + type: 'object', + required: [], + properties: { + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc20RenounceOwnership(fastify: FastifyInstance) { + fastify.post<{ + Params: erc20RenounceOwnershipRequestParams + Body: erc20RenounceOwnershipRequestBody + Reply: erc20RenounceOwnershipResponse + }>( + '/write/erc20/:chainId/:contractAddress/renounceOwnership', + { schema: erc20RenounceOwnershipSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc20Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('renounceOwnership', [ + + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending renounceOwnership transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'RenounceOwnership transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc20Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [], + functionName: 'renounceOwnership' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/write/transfer.ts b/src/routes/contract/extensions/erc20/write/transfer.ts index ef73710..da65725 100644 --- a/src/routes/contract/extensions/erc20/write/transfer.ts +++ b/src/routes/contract/extensions/erc20/write/transfer.ts @@ -1,204 +1,188 @@ import type { FastifyInstance } from 'fastify' - import { ethers } from 'ethers' import { erc20Abi } from '~/constants/abis/erc20' import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation } from '~/routes/contract/utils/tenderly/getSimulationUrl' import { TransactionService } from '~/services/transaction.service' import type { TransactionResponse } from '~/types/general' import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' import { getSigner } from '~/utils/wallet' -type ERC20TransferRequestBody = { - to: string - amount: string - waitForReceipt?: boolean +type erc20TransferRequestBody = { + to: string, value: string, + waitForReceipt?: boolean } -type ERC20TransferRequestParams = { - chainId: string - contractAddress: string +type erc20TransferRequestParams = { + chainId: string + contractAddress: string } -type ERC20TransferResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } +type erc20TransferResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } } -const ERC20TransferSchema = { - tags: ['ERC20'], - body: { - type: 'object', - required: ['to', 'amount'], - properties: { - to: { type: 'string' }, - amount: { type: 'string' }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - } - } +const erc20TransferSchema = { + tags: ['erc20'], + body: { + type: 'object', + required: ['to', 'value'], + properties: { + to: { type: 'string' }, + value: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } } export async function erc20Transfer(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC20TransferRequestParams - Body: ERC20TransferRequestBody - Reply: ERC20TransferResponse - }>( - '/write/erc20/:chainId/:contractAddress/transfer', - { - schema: ERC20TransferSchema - }, - async (request, reply) => { - logRequest(request) - - let tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { to, amount, waitForReceipt } = request.body - - const signer = await getSigner(chainId) - if (!signer || !signer.account?.address) { - logError(request, new Error('Signer not configured correctly.'), { - signer - }) - throw new Error('Signer not configured correctly.') - } - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - - const contract = new ethers.Contract(contractAddress, erc20Abi, signer) - logStep(request, 'Contract instance created', { contractAddress }) - - const data = contract.interface.encodeFunctionData('transfer', [ - to, - amount - ]) - logStep(request, 'Function data encoded', { to, amount }) - - const tx = { - to: contractAddress, - data - } - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - const txService = new TransactionService(fastify) - - logStep(request, 'Sending transfer transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - tx, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'Transfer transaction sent', { - txHash: txResponse.hash - }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc20Abi, - data: tx.data, - txHash: txHash, - isDeployTx: false, - args: [to, amount], - functionName: 'transfer' - }) - - logStep(request, 'Transfer transaction success', { - txHash: txHash - }) - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error ? error.message : 'Failed to execute transfer' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} + fastify.post<{ + Params: erc20TransferRequestParams + Body: erc20TransferRequestBody + Reply: erc20TransferResponse + }>( + '/write/erc20/:chainId/:contractAddress/transfer', + { schema: erc20TransferSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { to, value, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc20Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('transfer', [ + to, BigInt(value) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending transfer transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'Transfer transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc20Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [to, String(value)], + functionName: 'transfer' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/write/transferFrom.ts b/src/routes/contract/extensions/erc20/write/transferFrom.ts index dab89c1..c99d952 100644 --- a/src/routes/contract/extensions/erc20/write/transferFrom.ts +++ b/src/routes/contract/extensions/erc20/write/transferFrom.ts @@ -1,200 +1,189 @@ import type { FastifyInstance } from 'fastify' - import { ethers } from 'ethers' import { erc20Abi } from '~/constants/abis/erc20' import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation } from '~/routes/contract/utils/tenderly/getSimulationUrl' import { TransactionService } from '~/services/transaction.service' import type { TransactionResponse } from '~/types/general' import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' import { getSigner } from '~/utils/wallet' -type ERC20TransferFromRequestBody = { - from: string - to: string - amount: string - waitForReceipt?: boolean +type erc20TransferFromRequestBody = { + from: string, to: string, value: string, + waitForReceipt?: boolean } -type ERC20TransferFromRequestParams = { - chainId: string - contractAddress: string +type erc20TransferFromRequestParams = { + chainId: string + contractAddress: string } -type ERC20TransferFromResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } +type erc20TransferFromResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } } -const ERC20TransferFromSchema = { - tags: ['ERC20'], - body: { - type: 'object', - required: ['from', 'to', 'amount'], - properties: { - from: { type: 'string' }, - to: { type: 'string' }, - amount: { type: 'string' }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - } - } +const erc20TransferFromSchema = { + tags: ['erc20'], + body: { + type: 'object', + required: ['from', 'to', 'value'], + properties: { + from: { type: 'string' }, + to: { type: 'string' }, + value: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } } export async function erc20TransferFrom(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC20TransferFromRequestParams - Body: ERC20TransferFromRequestBody - Reply: ERC20TransferFromResponse - }>( - '/write/erc20/:chainId/:contractAddress/transferFrom', - { - schema: ERC20TransferFromSchema - }, - async (request, reply) => { - logRequest(request) - - let tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { from, to, amount, waitForReceipt } = request.body - - const signer = await getSigner(chainId) - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - - const contract = new ethers.Contract(contractAddress, erc20Abi, signer) - - const data = contract.interface.encodeFunctionData('transferFrom', [ - from, - to, - amount - ]) - - const tx = { - to: contractAddress, - data - } - logStep(request, 'Tx prepared', { tx }) - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - const txService = new TransactionService(fastify) - - logStep(request, 'Sending transferFrom transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - tx, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'TransferFrom transaction sent', { - txHash: txResponse.hash - }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc20Abi, - data: tx.data, - txHash: txHash, - isDeployTx: false, - args: [from, to, amount], - functionName: 'transferFrom' - }) - - logStep(request, 'TransferFrom transaction success', { - txHash: txHash - }) - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error ? error.message : 'Failed to execute transfer' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} + fastify.post<{ + Params: erc20TransferFromRequestParams + Body: erc20TransferFromRequestBody + Reply: erc20TransferFromResponse + }>( + '/write/erc20/:chainId/:contractAddress/transferFrom', + { schema: erc20TransferFromSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { from, to, value, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc20Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('transferFrom', [ + from, to, BigInt(value) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending transferFrom transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'TransferFrom transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc20Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [from, to, String(value)], + functionName: 'transferFrom' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc20/write/transferOwnership.ts b/src/routes/contract/extensions/erc20/write/transferOwnership.ts new file mode 100644 index 0000000..60baa6d --- /dev/null +++ b/src/routes/contract/extensions/erc20/write/transferOwnership.ts @@ -0,0 +1,187 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc20Abi } from '~/constants/abis/erc20' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc20TransferOwnershipRequestBody = { + newOwner: string, + waitForReceipt?: boolean +} + +type erc20TransferOwnershipRequestParams = { + chainId: string + contractAddress: string +} + +type erc20TransferOwnershipResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc20TransferOwnershipSchema = { + tags: ['erc20'], + body: { + type: 'object', + required: ['newOwner'], + properties: { + newOwner: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc20TransferOwnership(fastify: FastifyInstance) { + fastify.post<{ + Params: erc20TransferOwnershipRequestParams + Body: erc20TransferOwnershipRequestBody + Reply: erc20TransferOwnershipResponse + }>( + '/write/erc20/:chainId/:contractAddress/transferOwnership', + { schema: erc20TransferOwnershipSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { newOwner, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc20Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('transferOwnership', [ + newOwner + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending transferOwnership transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'TransferOwnership transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc20Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [newOwner], + functionName: 'transferOwnership' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721/erc721Items/write/batchBurn.ts b/src/routes/contract/extensions/erc721/erc721Items/write/batchBurn.ts deleted file mode 100644 index ccfae2b..0000000 --- a/src/routes/contract/extensions/erc721/erc721Items/write/batchBurn.ts +++ /dev/null @@ -1,208 +0,0 @@ -import type { FastifyInstance } from 'fastify' -import { encodeFunctionData } from 'viem' -import { erc721ItemsAbi } from '~/constants/abis/erc721Items' -import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation -} from '~/routes/contract/utils/tenderly/getSimulationUrl' -import { TransactionService } from '~/services/transaction.service' -import type { TransactionResponse } from '~/types/general' -import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' -import { getSigner } from '~/utils/wallet' - -type ERC721ItemsBatchBurnRequestBody = { - tokenIds: string[] - waitForReceipt?: boolean -} - -type ERC721ItemsBatchBurnRequestParams = { - chainId: string - contractAddress: string -} - -type ERC721ItemsBatchBurnResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } -} - -const ERC721ItemsBatchBurnSchema = { - tags: ['ERC721Items'], - description: - 'Burns multiple tokens on an ERC721Items contract in a single transaction.', - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - body: { - type: 'object', - required: ['tokenIds'], - properties: { - tokenIds: { - type: 'array', - items: { type: 'string' }, - description: 'An array of token IDs to burn.' - }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - } - } -} - -export async function erc721ItemsBatchBurn(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC721ItemsBatchBurnRequestParams - Body: ERC721ItemsBatchBurnRequestBody - Reply: ERC721ItemsBatchBurnResponse - }>( - '/write/erc721Items/:chainId/:contractAddress/batchBurn', - { - schema: ERC721ItemsBatchBurnSchema - }, - async (request, reply) => { - logRequest(request) - - let tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { tokenIds, waitForReceipt } = request.body - - const signer = await getSigner(chainId) - if (!signer || !signer.account?.address) { - logError(request, new Error('Signer not configured correctly.'), { - signer - }) - throw new Error('Signer not configured correctly.') - } - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - const txService = new TransactionService(fastify) - - const batchBurnData = encodeFunctionData({ - abi: erc721ItemsAbi, - functionName: 'batchBurn', - args: [tokenIds.map((id) => BigInt(id))] - }) - logStep(request, 'Function data encoded', { tokenIds }) - - const tx = { - to: contractAddress, - data: batchBurnData - } - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - logStep(request, 'Sending batchBurn transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - { - to: contractAddress, - data: batchBurnData - }, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'BatchBurn transaction sent', { txResponse }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc721ItemsAbi, - data: batchBurnData, - txHash: txHash, - functionName: 'batchBurn', - args: tokenIds, - isDeployTx: false - }) - logStep(request, 'Transaction record created in db') - - logStep(request, 'BatchBurn transaction success', { - txHash: txHash - }) - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error - ? error.message - : 'Unknown error during batchBurn' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc721/erc721Items/write/burn.ts b/src/routes/contract/extensions/erc721/erc721Items/write/burn.ts deleted file mode 100644 index b13abdc..0000000 --- a/src/routes/contract/extensions/erc721/erc721Items/write/burn.ts +++ /dev/null @@ -1,202 +0,0 @@ -import type { FastifyInstance } from 'fastify' -import { type Abi, encodeFunctionData } from 'viem' -import { getSigner } from '~/utils/wallet' - -import { erc721ItemsAbi } from '~/constants/abis/erc721Items' -import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation -} from '~/routes/contract/utils/tenderly/getSimulationUrl' -import { TransactionService } from '~/services/transaction.service' -import type { TransactionResponse } from '~/types/general' -import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' - -type ERC721ItemsBurnRequestBody = { - tokenId: string - waitForReceipt?: boolean -} - -type ERC721ItemsBurnRequestParams = { - chainId: string - contractAddress: string -} - -type ERC721ItemsBurnResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } -} - -const ERC721ItemsBurnSchema = { - tags: ['ERC721Items'], - description: 'Burns a specific token on an ERC721Items contract.', - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - body: { - type: 'object', - required: ['tokenId'], - properties: { - tokenId: { type: 'string', description: 'The ID of the token to burn.' }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - } - } -} - -export async function erc721ItemsBurn(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC721ItemsBurnRequestParams - Body: ERC721ItemsBurnRequestBody - Reply: ERC721ItemsBurnResponse - }>( - '/write/erc721Items/:chainId/:contractAddress/burn', - { - schema: ERC721ItemsBurnSchema - }, - async (request, reply) => { - logRequest(request) - - let tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { tokenId, waitForReceipt } = request.body - - const signer = await getSigner(chainId) - if (!signer || !signer.account?.address) { - logError(request, new Error('Signer not configured correctly.'), { - signer - }) - throw new Error('Signer not configured correctly.') - } - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - const txService = new TransactionService(fastify) - - const burnData = encodeFunctionData({ - abi: erc721ItemsAbi, - functionName: 'burn', - args: [BigInt(tokenId)] - }) - logStep(request, 'Function data encoded', { tokenId }) - - const tx = { - to: contractAddress, - data: burnData - } - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - logStep(request, 'Sending burn transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - { - to: contractAddress, - data: burnData - }, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'Burn transaction sent', { txResponse }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc721ItemsAbi, - data: burnData, - txHash: txHash, - functionName: 'burn', - args: [tokenId], - isDeployTx: false - }) - logStep(request, 'Transaction record created in db') - - logStep(request, 'Burn transaction success', { txHash: txHash }) - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error - ? error.message - : 'Unknown error during burn, please check that you own the NFT you are trying to burn' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc721/erc721Items/write/initialize.ts b/src/routes/contract/extensions/erc721/erc721Items/write/initialize.ts deleted file mode 100644 index e9640d5..0000000 --- a/src/routes/contract/extensions/erc721/erc721Items/write/initialize.ts +++ /dev/null @@ -1,298 +0,0 @@ -import type { FastifyInstance } from 'fastify' -import { encodeFunctionData, numberToHex, pad, zeroAddress } from 'viem' - -import { erc721ItemsAbi } from '~/constants/abis/erc721Items' -import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation -} from '~/routes/contract/utils/tenderly/getSimulationUrl' -import { TransactionService } from '~/services/transaction.service' -import type { TransactionResponse } from '~/types/general' -import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' -import { getSigner } from '~/utils/wallet' - -type ERC721ItemsInitializeRequestBody = { - owner: string - tokenName: string - tokenSymbol: string - tokenBaseURI: string - tokenContractURI: string - royaltyReceiver: string - royaltyFeeNumerator: string - implicitModeValidator: string | undefined | null - implicitModeProjectId: string | undefined | null - waitForReceipt?: boolean -} - -type ERC721ItemsInitializeRequestParams = { - chainId: string - contractAddress: string -} - -type ERC721ItemsInitializeResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } -} - -const ERC721ItemsInitializeSchema = { - tags: ['ERC721Items'], - description: 'Calls the initialize function on an ERC721Items contract.', - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - body: { - type: 'object', - required: [ - 'owner', - 'tokenName', - 'tokenSymbol', - 'tokenBaseURI', - 'tokenContractURI', - 'royaltyReceiver', - 'royaltyFeeNumerator', - 'implicitModeValidator', - 'implicitModeProjectId' - ], - properties: { - owner: { type: 'string', description: 'Address of the contract owner' }, - tokenName: { type: 'string', description: 'Name of the token' }, - tokenSymbol: { type: 'string', description: 'Symbol of the token' }, - tokenBaseURI: { - type: 'string', - description: 'Base URI for token metadata' - }, - tokenContractURI: { - type: 'string', - description: 'Contract URI for collection metadata' - }, - royaltyReceiver: { - type: 'string', - description: 'Address to receive royalties' - }, - royaltyFeeNumerator: { - type: 'string', - description: 'Royalty fee numerator (e.g., 500 for 5%)' - }, - implicitModeValidator: { - type: 'string', - description: 'Address of the implicit mode validator', - nullable: true - }, - implicitModeProjectId: { - type: 'string', - description: 'Implicit mode project ID', - nullable: true - }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - }, - 400: { - type: 'object', - properties: { - error: { type: 'string' } - } - }, - 500: { - type: 'object', - properties: { - error: { type: 'string' } - } - } - } -} - -export async function erc721ItemsInitialize(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC721ItemsInitializeRequestParams - Body: ERC721ItemsInitializeRequestBody - Reply: ERC721ItemsInitializeResponse - }>( - '/write/erc721Items/:chainId/:contractAddress/initialize', - { - schema: ERC721ItemsInitializeSchema - }, - async (request, reply) => { - logRequest(request) - - let tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { - owner, - tokenName, - tokenSymbol, - tokenBaseURI, - tokenContractURI, - royaltyReceiver, - royaltyFeeNumerator, - implicitModeValidator, - implicitModeProjectId, - waitForReceipt - } = request.body - - const signer = await getSigner(chainId) - if (!signer || !signer.account?.address) { - logError(request, new Error('Signer not configured correctly.'), { - signer - }) - throw new Error('Signer not configured correctly.') - } - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - const txService = new TransactionService(fastify) - - const initializeData = encodeFunctionData({ - abi: erc721ItemsAbi, - functionName: 'initialize', - args: [ - owner, - tokenName, - tokenSymbol, - tokenBaseURI, - tokenContractURI, - royaltyReceiver, - BigInt(royaltyFeeNumerator ?? 0), - implicitModeValidator ?? zeroAddress, - pad(numberToHex(Number(implicitModeProjectId ?? 0)), { size: 32 }) - ] - }) - logStep(request, 'Function data encoded', { - owner, - tokenName, - tokenSymbol - }) - - const tx = { - to: contractAddress, - data: initializeData - } - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - logStep(request, 'Sending initialize transaction...') - console.log('waitForReceipt', waitForReceipt) - const txResponse: TransactionResponse = await signer.sendTransaction( - { - to: contractAddress, - data: initializeData - }, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'Initialize transaction sent', { txResponse }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc721ItemsAbi, - data: initializeData, - txHash: txHash, - functionName: 'initialize', - args: [ - owner, - tokenName, - tokenSymbol, - tokenBaseURI, - tokenContractURI, - royaltyReceiver, - royaltyFeeNumerator, - implicitModeValidator ?? zeroAddress, - pad(numberToHex(Number(implicitModeProjectId ?? 0)), { size: 32 }) - ], - isDeployTx: false - }) - logStep(request, 'Transaction record created in db') - - logStep(request, 'Initialize transaction success', { - txHash: txHash - }) - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error - ? error.message - : 'Unknown error during initialization' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc721/erc721Items/write/mint.ts b/src/routes/contract/extensions/erc721/erc721Items/write/mint.ts deleted file mode 100644 index bfeb654..0000000 --- a/src/routes/contract/extensions/erc721/erc721Items/write/mint.ts +++ /dev/null @@ -1,205 +0,0 @@ -import type { FastifyInstance } from 'fastify' - -import { ethers } from 'ethers' -import { erc721ItemsAbi } from '~/constants/abis/erc721Items' -import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation -} from '~/routes/contract/utils/tenderly/getSimulationUrl' -import { TransactionService } from '~/services/transaction.service' -import type { TransactionResponse } from '~/types/general' -import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' -import { getSigner } from '~/utils/wallet' - -type ERC721ItemsMintRequestBody = { - to: string - tokenId: string - waitForReceipt?: boolean -} - -type ERC721ItemsMintRequestParams = { - chainId: string - contractAddress: string -} - -type ERC721ItemsMintResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } -} - -const ERC721ItemsMintSchema = { - tags: ['ERC721Items'], - body: { - type: 'object', - required: ['to', 'tokenId'], - properties: { - to: { type: 'string' }, - tokenId: { type: 'string' }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - } - } -} - -export async function erc721ItemsMint(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC721ItemsMintRequestParams - Body: ERC721ItemsMintRequestBody - Reply: ERC721ItemsMintResponse - }>( - '/write/erc721Items/:chainId/:contractAddress/mint', - { schema: ERC721ItemsMintSchema }, - async (request, reply) => { - logRequest(request) - - let tenderlyUrl: string | null = null - let txHash: string | null = null - - const { to, tokenId, waitForReceipt } = request.body - const { chainId, contractAddress } = request.params - - try { - const signer = await getSigner(chainId) - if (!signer || !signer.account?.address) { - logError(request, new Error('Signer not configured correctly.'), { - signer - }) - throw new Error('Signer not configured correctly.') - } - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - - const contract = new ethers.Contract( - contractAddress, - erc721ItemsAbi, - signer - ) - logStep(request, 'Contract instance created') - - const data = contract.interface.encodeFunctionData('mint', [ - to, - tokenId - ]) - logStep(request, 'Function data encoded', { to, tokenId }) - - const tx = { - to: contractAddress, - data - } - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - const txService = new TransactionService(fastify) - - logStep(request, 'Sending mint transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - tx, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'Mint transaction sent', { txResponse }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc721ItemsAbi, - data: tx.data, - txHash: txHash, - isDeployTx: false, - args: [to, tokenId], - functionName: 'mint' - }) - - logStep(request, 'Mint transaction success', { - txHash: txHash - }) - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: - error instanceof Error - ? error.message - : 'Failed to mint ERC721Items' - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc721/index.ts b/src/routes/contract/extensions/erc721/index.ts new file mode 100644 index 0000000..1aec10b --- /dev/null +++ b/src/routes/contract/extensions/erc721/index.ts @@ -0,0 +1,33 @@ +import type { FastifyInstance } from 'fastify' + +import { erc721Approve } from './write/approve' +import { erc721SafeTransferFrom_1 } from './write/safeTransferFrom_1' +import { erc721SafeTransferFrom_2 } from './write/safeTransferFrom_2' +import { erc721SetApprovalForAll } from './write/setApprovalForAll' +import { erc721TransferFrom } from './write/transferFrom' +import { erc721TotalSupply } from './read/totalSupply' +import { erc721BalanceOf } from './read/balanceOf' +import { erc721GetApproved } from './read/getApproved' +import { erc721IsApprovedForAll } from './read/isApprovedForAll' +import { erc721Name } from './read/name' +import { erc721OwnerOf } from './read/ownerOf' +import { erc721SupportsInterface } from './read/supportsInterface' +import { erc721Symbol } from './read/symbol' +import { erc721TokenURI } from './read/tokenURI' + +export function registerErc721Routes(fastify: FastifyInstance) { + erc721Approve(fastify) + erc721SafeTransferFrom_1(fastify) + erc721SafeTransferFrom_2(fastify) + erc721SetApprovalForAll(fastify) + erc721TransferFrom(fastify) + erc721TotalSupply(fastify) + erc721BalanceOf(fastify) + erc721GetApproved(fastify) + erc721IsApprovedForAll(fastify) + erc721Name(fastify) + erc721OwnerOf(fastify) + erc721SupportsInterface(fastify) + erc721Symbol(fastify) + erc721TokenURI(fastify) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721/read/balanceOf.ts b/src/routes/contract/extensions/erc721/read/balanceOf.ts index 2e88073..5ba5f29 100644 --- a/src/routes/contract/extensions/erc721/read/balanceOf.ts +++ b/src/routes/contract/extensions/erc721/read/balanceOf.ts @@ -1,97 +1,120 @@ -import { ethers } from 'ethers' import type { FastifyInstance } from 'fastify' -import { erc721Abi } from 'viem' -import { getSigner } from '../../../../../utils/wallet' +import { ethers } from 'ethers' +import { erc721Abi } from '~/constants/abis/erc721' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} -type ERC721BalanceOfRequestQuery = { - owner: string +type erc721BalanceOfRequestQuery = { + owner: string } -type ERC721BalanceOfRequestParams = { - chainId: string - contractAddress: string +type erc721BalanceOfRequestParams = { + chainId: string + contractAddress: string } -type ERC721BalanceOfResponse = { - result?: { - data: unknown - error?: string - } +type erc721BalanceOfResponse = { + result?: { + data: any + error?: string + } } -const ERC721BalanceOfSchema = { - tags: ['ERC721'], - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - query: { - type: 'object', - required: ['owner'], - properties: { - owner: { type: 'string' } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - data: { type: 'string' }, - error: { type: 'string', nullable: true } - } - } - } - } - } +const erc721BalanceOfSchema = { + tags: ['erc721'], + querystring: { + type: 'object', + required: ['owner'], + properties: { + owner: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } } export async function erc721BalanceOf(fastify: FastifyInstance) { - fastify.get<{ - Params: ERC721BalanceOfRequestParams - Querystring: ERC721BalanceOfRequestQuery - Reply: ERC721BalanceOfResponse - }>( - '/read/erc721/:chainId/:contractAddress/balanceOf', - { - schema: ERC721BalanceOfSchema - }, - async (request, reply) => { - try { - const { chainId, contractAddress } = request.params - const { owner } = request.query + fastify.get<{ + Params: erc721BalanceOfRequestParams + Querystring: erc721BalanceOfRequestQuery + Reply: erc721BalanceOfResponse + }>( + '/read/erc721/:chainId/:contractAddress/balanceOf', + { schema: erc721BalanceOfSchema }, + async (request, reply) => { + const { owner } = request.query + const { chainId, contractAddress } = request.params - const provider = await getSigner(chainId) - const contract = new ethers.Contract( - contractAddress, - erc721Abi, - provider - ) + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } - const data = await contract.balanceOf(owner) - console.log('Balance of ', owner, ' is ', data) + const contract = new ethers.Contract( + contractAddress, + erc721Abi, + signer + ) - return reply.code(200).send({ - result: { - data: data.toString() - } - }) - } catch (error) { - request.log.error(error) - return reply.code(500).send({ - result: { - data: null, - error: - error instanceof Error ? error.message : 'Failed to read balance' - } - }) - } - } - ) -} + const result = await contract.balanceOf(owner) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721/read/getApproved.ts b/src/routes/contract/extensions/erc721/read/getApproved.ts new file mode 100644 index 0000000..427c5c1 --- /dev/null +++ b/src/routes/contract/extensions/erc721/read/getApproved.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721Abi } from '~/constants/abis/erc721' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721GetApprovedRequestQuery = { + tokenId: string +} + +type erc721GetApprovedRequestParams = { + chainId: string + contractAddress: string +} + +type erc721GetApprovedResponse = { + result?: { + data: any + error?: string + } +} + +const erc721GetApprovedSchema = { + tags: ['erc721'], + querystring: { + type: 'object', + required: ['tokenId'], + properties: { + tokenId: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721GetApproved(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721GetApprovedRequestParams + Querystring: erc721GetApprovedRequestQuery + Reply: erc721GetApprovedResponse + }>( + '/read/erc721/:chainId/:contractAddress/getApproved', + { schema: erc721GetApprovedSchema }, + async (request, reply) => { + const { tokenId } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721Abi, + signer + ) + + const result = await contract.getApproved(tokenId) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721/read/isApprovedForAll.ts b/src/routes/contract/extensions/erc721/read/isApprovedForAll.ts new file mode 100644 index 0000000..149f4ae --- /dev/null +++ b/src/routes/contract/extensions/erc721/read/isApprovedForAll.ts @@ -0,0 +1,121 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721Abi } from '~/constants/abis/erc721' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721IsApprovedForAllRequestQuery = { + owner: string, operator: string +} + +type erc721IsApprovedForAllRequestParams = { + chainId: string + contractAddress: string +} + +type erc721IsApprovedForAllResponse = { + result?: { + data: any + error?: string + } +} + +const erc721IsApprovedForAllSchema = { + tags: ['erc721'], + querystring: { + type: 'object', + required: ['owner', 'operator'], + properties: { + owner: { type: 'string' }, + operator: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721IsApprovedForAll(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721IsApprovedForAllRequestParams + Querystring: erc721IsApprovedForAllRequestQuery + Reply: erc721IsApprovedForAllResponse + }>( + '/read/erc721/:chainId/:contractAddress/isApprovedForAll', + { schema: erc721IsApprovedForAllSchema }, + async (request, reply) => { + const { owner, operator } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721Abi, + signer + ) + + const result = await contract.isApprovedForAll(owner, operator) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721/read/name.ts b/src/routes/contract/extensions/erc721/read/name.ts new file mode 100644 index 0000000..2d1bfe9 --- /dev/null +++ b/src/routes/contract/extensions/erc721/read/name.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721Abi } from '~/constants/abis/erc721' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721NameRequestParams = { + chainId: string + contractAddress: string +} + +type erc721NameResponse = { + result?: { + data: any + error?: string + } +} + +const erc721NameSchema = { + tags: ['erc721'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721Name(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721NameRequestParams + + Reply: erc721NameResponse + }>( + '/read/erc721/:chainId/:contractAddress/name', + { schema: erc721NameSchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721Abi, + signer + ) + + const result = await contract.name() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721/read/ownerOf.ts b/src/routes/contract/extensions/erc721/read/ownerOf.ts new file mode 100644 index 0000000..c830460 --- /dev/null +++ b/src/routes/contract/extensions/erc721/read/ownerOf.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721Abi } from '~/constants/abis/erc721' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721OwnerOfRequestQuery = { + tokenId: string +} + +type erc721OwnerOfRequestParams = { + chainId: string + contractAddress: string +} + +type erc721OwnerOfResponse = { + result?: { + data: any + error?: string + } +} + +const erc721OwnerOfSchema = { + tags: ['erc721'], + querystring: { + type: 'object', + required: ['tokenId'], + properties: { + tokenId: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721OwnerOf(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721OwnerOfRequestParams + Querystring: erc721OwnerOfRequestQuery + Reply: erc721OwnerOfResponse + }>( + '/read/erc721/:chainId/:contractAddress/ownerOf', + { schema: erc721OwnerOfSchema }, + async (request, reply) => { + const { tokenId } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721Abi, + signer + ) + + const result = await contract.ownerOf(tokenId) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721/read/supportsInterface.ts b/src/routes/contract/extensions/erc721/read/supportsInterface.ts new file mode 100644 index 0000000..2f7c48e --- /dev/null +++ b/src/routes/contract/extensions/erc721/read/supportsInterface.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721Abi } from '~/constants/abis/erc721' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721SupportsInterfaceRequestQuery = { + interfaceId: string +} + +type erc721SupportsInterfaceRequestParams = { + chainId: string + contractAddress: string +} + +type erc721SupportsInterfaceResponse = { + result?: { + data: any + error?: string + } +} + +const erc721SupportsInterfaceSchema = { + tags: ['erc721'], + querystring: { + type: 'object', + required: ['interfaceId'], + properties: { + interfaceId: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721SupportsInterface(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721SupportsInterfaceRequestParams + Querystring: erc721SupportsInterfaceRequestQuery + Reply: erc721SupportsInterfaceResponse + }>( + '/read/erc721/:chainId/:contractAddress/supportsInterface', + { schema: erc721SupportsInterfaceSchema }, + async (request, reply) => { + const { interfaceId } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721Abi, + signer + ) + + const result = await contract.supportsInterface(interfaceId) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721/read/symbol.ts b/src/routes/contract/extensions/erc721/read/symbol.ts new file mode 100644 index 0000000..04af3cb --- /dev/null +++ b/src/routes/contract/extensions/erc721/read/symbol.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721Abi } from '~/constants/abis/erc721' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721SymbolRequestParams = { + chainId: string + contractAddress: string +} + +type erc721SymbolResponse = { + result?: { + data: any + error?: string + } +} + +const erc721SymbolSchema = { + tags: ['erc721'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721Symbol(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721SymbolRequestParams + + Reply: erc721SymbolResponse + }>( + '/read/erc721/:chainId/:contractAddress/symbol', + { schema: erc721SymbolSchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721Abi, + signer + ) + + const result = await contract.symbol() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721/read/tokenURI.ts b/src/routes/contract/extensions/erc721/read/tokenURI.ts new file mode 100644 index 0000000..f54fa94 --- /dev/null +++ b/src/routes/contract/extensions/erc721/read/tokenURI.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721Abi } from '~/constants/abis/erc721' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721TokenURIRequestQuery = { + tokenId: string +} + +type erc721TokenURIRequestParams = { + chainId: string + contractAddress: string +} + +type erc721TokenURIResponse = { + result?: { + data: any + error?: string + } +} + +const erc721TokenURISchema = { + tags: ['erc721'], + querystring: { + type: 'object', + required: ['tokenId'], + properties: { + tokenId: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721TokenURI(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721TokenURIRequestParams + Querystring: erc721TokenURIRequestQuery + Reply: erc721TokenURIResponse + }>( + '/read/erc721/:chainId/:contractAddress/tokenURI', + { schema: erc721TokenURISchema }, + async (request, reply) => { + const { tokenId } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721Abi, + signer + ) + + const result = await contract.tokenURI(tokenId) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721/read/totalSupply.ts b/src/routes/contract/extensions/erc721/read/totalSupply.ts new file mode 100644 index 0000000..5196ea7 --- /dev/null +++ b/src/routes/contract/extensions/erc721/read/totalSupply.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721Abi } from '~/constants/abis/erc721' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721TotalSupplyRequestParams = { + chainId: string + contractAddress: string +} + +type erc721TotalSupplyResponse = { + result?: { + data: any + error?: string + } +} + +const erc721TotalSupplySchema = { + tags: ['erc721'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721TotalSupply(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721TotalSupplyRequestParams + + Reply: erc721TotalSupplyResponse + }>( + '/read/erc721/:chainId/:contractAddress/totalSupply', + { schema: erc721TotalSupplySchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721Abi, + signer + ) + + const result = await contract.totalSupply() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721/write/approve.ts b/src/routes/contract/extensions/erc721/write/approve.ts new file mode 100644 index 0000000..0abd9ab --- /dev/null +++ b/src/routes/contract/extensions/erc721/write/approve.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721Abi } from '~/constants/abis/erc721' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ApproveRequestBody = { + to: string, tokenId: string, + waitForReceipt?: boolean +} + +type erc721ApproveRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ApproveResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ApproveSchema = { + tags: ['erc721'], + body: { + type: 'object', + required: ['to', 'tokenId'], + properties: { + to: { type: 'string' }, + tokenId: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721Approve(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ApproveRequestParams + Body: erc721ApproveRequestBody + Reply: erc721ApproveResponse + }>( + '/write/erc721/:chainId/:contractAddress/approve', + { schema: erc721ApproveSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { to, tokenId, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('approve', [ + to, BigInt(tokenId) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending approve transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'Approve transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [to, String(tokenId)], + functionName: 'approve' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721/write/burn.ts b/src/routes/contract/extensions/erc721/write/burn.ts deleted file mode 100644 index aa145ad..0000000 --- a/src/routes/contract/extensions/erc721/write/burn.ts +++ /dev/null @@ -1,191 +0,0 @@ -import type { FastifyInstance } from 'fastify' - -import { ethers } from 'ethers' -import { erc721Abi } from '~/constants/abis/erc721' -import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation -} from '~/routes/contract/utils/tenderly/getSimulationUrl' -import { TransactionService } from '~/services/transaction.service' -import type { TransactionResponse } from '~/types/general' -import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' -import { getSigner } from '~/utils/wallet' - -type ERC721BurnRequestBody = { - tokenId: string - waitForReceipt?: boolean -} - -type ERC721BurnRequestParams = { - chainId: string - contractAddress: string -} - -type ERC721BurnResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } -} - -const ERC721BurnSchema = { - tags: ['ERC721'], - body: { - type: 'object', - required: ['tokenId'], - properties: { - tokenId: { type: 'string' }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - } - } -} - -export async function erc721Burn(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC721BurnRequestParams - Body: ERC721BurnRequestBody - Reply: ERC721BurnResponse - }>( - '/write/erc721/:chainId/:contractAddress/burn', - { - schema: ERC721BurnSchema - }, - async (request, reply) => { - logRequest(request) - - let tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { tokenId, waitForReceipt } = request.body - - const signer = await getSigner(chainId) - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - - const contract = new ethers.Contract(contractAddress, erc721Abi, signer) - logStep(request, 'Contract instance created') - - const data = contract.interface.encodeFunctionData('burn', [tokenId]) - logStep(request, 'Function data encoded') - - const tx = { - to: contractAddress, - data - } - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - const txService = new TransactionService(fastify) - - logStep(request, 'Sending burn transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - tx, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'Burn transaction sent', { txResponse }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc721Abi, - data: tx.data, - txHash: txHash, - isDeployTx: false, - args: [tokenId], - functionName: 'burn' - }) - - logStep(request, 'Burn transaction success', { txHash: txHash }) - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error - ? error.message - : 'Unknown error during burn, please check that you own the NFT you are trying to burn' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc721/write/mint.ts b/src/routes/contract/extensions/erc721/write/mint.ts deleted file mode 100644 index a967830..0000000 --- a/src/routes/contract/extensions/erc721/write/mint.ts +++ /dev/null @@ -1,209 +0,0 @@ -import type { FastifyInstance } from 'fastify' - -import { ethers } from 'ethers' -import { erc721Abi } from '~/constants/abis/erc721' -import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation -} from '~/routes/contract/utils/tenderly/getSimulationUrl' -import { TransactionService } from '~/services/transaction.service' -import type { TransactionResponse } from '~/types/general' -import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' -import { getSigner } from '~/utils/wallet' - -type ERC721MintRequestBody = { - to: string - tokenId: string - waitForReceipt?: boolean -} - -type ERC721MintRequestParams = { - chainId: string - contractAddress: string -} - -type ERC721MintResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } -} - -const ERC721MintSchema = { - tags: ['ERC721'], - body: { - type: 'object', - required: ['to', 'tokenId'], - properties: { - to: { type: 'string' }, - tokenId: { type: 'string' }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - error: { type: 'string', nullable: true } - } - } - } - }, - 500: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string', nullable: true }, - txUrl: { type: 'string', nullable: true }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string' } - } - } - } - } - } -} - -export async function erc721Mint(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC721MintRequestParams - Body: ERC721MintRequestBody - Reply: ERC721MintResponse - }>( - '/write/erc721/:chainId/:contractAddress/mint', - { - schema: ERC721MintSchema - }, - async (request, reply) => { - logRequest(request) - - let tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { to, tokenId, waitForReceipt } = request.body - - const signer = await getSigner(chainId) - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - - const contract = new ethers.Contract(contractAddress, erc721Abi, signer) - logStep(request, 'Contract instance created') - - const data = contract.interface.encodeFunctionData('mint', [ - to, - tokenId - ]) - logStep(request, 'Function data encoded') - - const tx = { - to: contractAddress, - data - } - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - const txService = new TransactionService(fastify) - - logStep(request, 'Sending mint transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - tx, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'Mint transaction sent', { txResponse }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc721Abi, - data: tx.data, - txHash: txHash, - isDeployTx: false, - args: [to, tokenId], - functionName: 'mint' - }) - - logStep(request, 'Mint transaction success', { - txHash: txHash - }) - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error ? error.message : 'Failed to mint NFT' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc721/write/safeMint.ts b/src/routes/contract/extensions/erc721/write/safeMint.ts deleted file mode 100644 index 93043b1..0000000 --- a/src/routes/contract/extensions/erc721/write/safeMint.ts +++ /dev/null @@ -1,193 +0,0 @@ -import type { FastifyInstance } from 'fastify' - -import { ethers } from 'ethers' -import { erc721Abi } from '~/constants/abis/erc721' -import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation -} from '~/routes/contract/utils/tenderly/getSimulationUrl' -import { TransactionService } from '~/services/transaction.service' -import type { TransactionResponse } from '~/types/general' -import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' -import { getSigner } from '~/utils/wallet' - -type ERC721SafeMintRequestBody = { - to: string - tokenId: string - waitForReceipt?: boolean -} - -type ERC721SafeMintRequestParams = { - chainId: string - contractAddress: string -} - -type ERC721SafeMintResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } -} - -const ERC721SafeMintSchema = { - tags: ['ERC721'], - body: { - type: 'object', - required: ['to', 'tokenId'], - properties: { - to: { type: 'string' }, - tokenId: { type: 'string' }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - } - } -} - -export async function erc721SafeMint(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC721SafeMintRequestParams - Body: ERC721SafeMintRequestBody - Reply: ERC721SafeMintResponse - }>( - '/write/erc721/:chainId/:contractAddress/safeMint', - { - schema: ERC721SafeMintSchema - }, - async (request, reply) => { - logRequest(request) - - let tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { to, tokenId, waitForReceipt } = request.body - - const signer = await getSigner(chainId) - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - - const contract = new ethers.Contract(contractAddress, erc721Abi, signer) - - const data = contract.interface.encodeFunctionData('safeMint', [ - to, - tokenId - ]) - logStep(request, 'Function data encoded') - - const tx = { - to: contractAddress, - data - } - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - [tx], - Number(chainId) - ) - tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - const txService = new TransactionService(fastify) - - logStep(request, 'Sending safeMint transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - tx, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'SafeMint transaction sent', { txResponse }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc721Abi, - data: tx.data, - txHash: txHash, - isDeployTx: false, - args: [to, tokenId], - functionName: 'safeMint' - }) - - logStep(request, 'SafeMint transaction success', { txHash: txHash }) - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error ? error.message : 'Failed to mint NFT' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc721/write/safeMintBatch.ts b/src/routes/contract/extensions/erc721/write/safeMintBatch.ts deleted file mode 100644 index 0f8931a..0000000 --- a/src/routes/contract/extensions/erc721/write/safeMintBatch.ts +++ /dev/null @@ -1,201 +0,0 @@ -import type { FastifyInstance } from 'fastify' - -import { ethers } from 'ethers' -import { erc721Abi } from '~/constants/abis/erc721' -import { - getTenderlySimulationUrl, - prepareTransactionsForTenderlySimulation -} from '~/routes/contract/utils/tenderly/getSimulationUrl' -import { TransactionService } from '~/services/transaction.service' -import type { TransactionResponse } from '~/types/general' -import { logError, logRequest, logStep } from '~/utils/loggingUtils' -import { - extractTxHashFromErrorReceipt, - getBlockExplorerUrl -} from '~/utils/other' -import { getSigner } from '~/utils/wallet' - -type ERC721SafeMintBatchRequestBody = { - recipients: string[] - tokenIds: string[] - waitForReceipt?: boolean -} - -type ERC721SafeMintBatchRequestParams = { - chainId: string - contractAddress: string -} - -type ERC721SafeMintBatchResponse = { - result?: { - txHash: string | null - txUrl: string | null - txSimulationUrl?: string | null - error?: string - } -} - -const ERC721SafeMintBatchSchema = { - tags: ['ERC721'], - body: { - type: 'object', - required: ['recipients', 'tokenIds'], - properties: { - recipients: { type: 'array', items: { type: 'string' } }, - tokenIds: { type: 'array', items: { type: 'string' } }, - waitForReceipt: { type: 'boolean', nullable: true } - } - }, - params: { - type: 'object', - required: ['chainId', 'contractAddress'], - properties: { - chainId: { type: 'string' }, - contractAddress: { type: 'string' } - } - }, - headers: { - type: 'object', - properties: { - 'x-secret-key': { type: 'string', nullable: true } - } - }, - response: { - 200: { - type: 'object', - properties: { - result: { - type: 'object', - properties: { - txHash: { type: 'string' }, - txUrl: { type: 'string' }, - txSimulationUrl: { type: 'string', nullable: true }, - error: { type: 'string', nullable: true } - } - } - } - } - } -} - -export async function erc721SafeMintBatch(fastify: FastifyInstance) { - fastify.post<{ - Params: ERC721SafeMintBatchRequestParams - Body: ERC721SafeMintBatchRequestBody - Reply: ERC721SafeMintBatchResponse - }>( - '/write/erc721/:chainId/:contractAddress/safeMintBatch', - { - schema: ERC721SafeMintBatchSchema - }, - async (request, reply) => { - logRequest(request) - - let tenderlyUrl: string | null = null - let txHash: string | null = null - const { chainId, contractAddress } = request.params - - try { - const { recipients, tokenIds, waitForReceipt } = request.body - - const signer = await getSigner(chainId) - logStep(request, 'Tx signer received', { - signer: signer.account.address - }) - - const contract = new ethers.Contract(contractAddress, erc721Abi, signer) - logStep(request, 'Contract instance created') - - const txs = recipients.map((recipient, index) => { - const data = contract.interface.encodeFunctionData('safeMint', [ - recipient, - tokenIds[index] - ]) - return { - to: contractAddress, - data - } - }) - logStep(request, 'Function data encoded for batch', { - count: txs.length - }) - - const { simulationData, signedTx } = - await prepareTransactionsForTenderlySimulation( - signer, - txs, - Number(chainId) - ) - tenderlyUrl = getTenderlySimulationUrl({ - chainId: chainId, - gas: 3000000, - block: await signer.provider.getBlockNumber(), - blockIndex: 0, - contractAddress: signedTx.entrypoint, - rawFunctionInput: simulationData - }) - - const txService = new TransactionService(fastify) - - logStep(request, 'Sending safeMintBatch transaction...') - const txResponse: TransactionResponse = await signer.sendTransaction( - txs, - { waitForReceipt: waitForReceipt ?? false } - ) - txHash = txResponse.hash - logStep(request, 'SafeMintBatch transaction sent', { txResponse }) - - if (txResponse.receipt?.status === 0) { - throw new Error('Transaction reverted', { cause: txResponse.receipt }) - } - - txs.forEach(async (tx, index) => { - await txService.createTransaction({ - chainId, - contractAddress, - abi: erc721Abi, - data: tx.data, - txHash: txHash ?? '', - isDeployTx: false, - args: [recipients[index], tokenIds[index]], - functionName: 'safeMint' - }) - }) - - logStep(request, 'SafeMintBatch transaction success', { - txHash: txHash - }) - return reply.code(200).send({ - result: { - txHash: txHash, - txUrl: getBlockExplorerUrl(Number(chainId), txHash), - txSimulationUrl: tenderlyUrl ?? null - } - }) - } catch (error) { - // Extract transaction hash from error receipt if available - const errorTxHash = extractTxHashFromErrorReceipt(error) - const finalTxHash = txHash ?? errorTxHash - - logError(request, error, { - params: request.params, - body: request.body, - txHash: finalTxHash - }) - - const errorMessage = - error instanceof Error ? error.message : 'Failed to mint NFT' - return reply.code(500).send({ - result: { - txHash: finalTxHash, - txUrl: finalTxHash - ? getBlockExplorerUrl(Number(chainId), finalTxHash) - : null, - txSimulationUrl: tenderlyUrl ?? null, - error: errorMessage - } - }) - } - } - ) -} diff --git a/src/routes/contract/extensions/erc721/write/safeTransferFrom_1.ts b/src/routes/contract/extensions/erc721/write/safeTransferFrom_1.ts new file mode 100644 index 0000000..591383f --- /dev/null +++ b/src/routes/contract/extensions/erc721/write/safeTransferFrom_1.ts @@ -0,0 +1,189 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721Abi } from '~/constants/abis/erc721' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721SafeTransferFrom_1RequestBody = { + from: string, to: string, tokenId: string, + waitForReceipt?: boolean +} + +type erc721SafeTransferFrom_1RequestParams = { + chainId: string + contractAddress: string +} + +type erc721SafeTransferFrom_1Response = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721SafeTransferFrom_1Schema = { + tags: ['erc721'], + body: { + type: 'object', + required: ['from', 'to', 'tokenId'], + properties: { + from: { type: 'string' }, + to: { type: 'string' }, + tokenId: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721SafeTransferFrom_1(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721SafeTransferFrom_1RequestParams + Body: erc721SafeTransferFrom_1RequestBody + Reply: erc721SafeTransferFrom_1Response + }>( + '/write/erc721/:chainId/:contractAddress/safeTransferFrom_1', + { schema: erc721SafeTransferFrom_1Schema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { from, to, tokenId, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('safeTransferFrom', [ + from, to, BigInt(tokenId) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending safeTransferFrom transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SafeTransferFrom transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [from, to, String(tokenId)], + functionName: 'safeTransferFrom' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721/write/safeTransferFrom_2.ts b/src/routes/contract/extensions/erc721/write/safeTransferFrom_2.ts new file mode 100644 index 0000000..a9ddff6 --- /dev/null +++ b/src/routes/contract/extensions/erc721/write/safeTransferFrom_2.ts @@ -0,0 +1,190 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721Abi } from '~/constants/abis/erc721' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721SafeTransferFrom_2RequestBody = { + from: string, to: string, tokenId: string, data: string, + waitForReceipt?: boolean +} + +type erc721SafeTransferFrom_2RequestParams = { + chainId: string + contractAddress: string +} + +type erc721SafeTransferFrom_2Response = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721SafeTransferFrom_2Schema = { + tags: ['erc721'], + body: { + type: 'object', + required: ['from', 'to', 'tokenId', 'data'], + properties: { + from: { type: 'string' }, + to: { type: 'string' }, + tokenId: { type: 'string' }, + data: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721SafeTransferFrom_2(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721SafeTransferFrom_2RequestParams + Body: erc721SafeTransferFrom_2RequestBody + Reply: erc721SafeTransferFrom_2Response + }>( + '/write/erc721/:chainId/:contractAddress/safeTransferFrom_2', + { schema: erc721SafeTransferFrom_2Schema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { from, to, tokenId, data, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('safeTransferFrom', [ + from, to, BigInt(tokenId), data + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending safeTransferFrom transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SafeTransferFrom transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [from, to, String(tokenId), data], + functionName: 'safeTransferFrom' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721/write/setApprovalForAll.ts b/src/routes/contract/extensions/erc721/write/setApprovalForAll.ts new file mode 100644 index 0000000..798947a --- /dev/null +++ b/src/routes/contract/extensions/erc721/write/setApprovalForAll.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721Abi } from '~/constants/abis/erc721' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721SetApprovalForAllRequestBody = { + operator: string, _approved: string, + waitForReceipt?: boolean +} + +type erc721SetApprovalForAllRequestParams = { + chainId: string + contractAddress: string +} + +type erc721SetApprovalForAllResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721SetApprovalForAllSchema = { + tags: ['erc721'], + body: { + type: 'object', + required: ['operator', '_approved'], + properties: { + operator: { type: 'string' }, + _approved: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721SetApprovalForAll(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721SetApprovalForAllRequestParams + Body: erc721SetApprovalForAllRequestBody + Reply: erc721SetApprovalForAllResponse + }>( + '/write/erc721/:chainId/:contractAddress/setApprovalForAll', + { schema: erc721SetApprovalForAllSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { operator, _approved, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setApprovalForAll', [ + operator, _approved + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setApprovalForAll transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetApprovalForAll transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [operator, _approved], + functionName: 'setApprovalForAll' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721/write/transferFrom.ts b/src/routes/contract/extensions/erc721/write/transferFrom.ts new file mode 100644 index 0000000..6279dbc --- /dev/null +++ b/src/routes/contract/extensions/erc721/write/transferFrom.ts @@ -0,0 +1,189 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721Abi } from '~/constants/abis/erc721' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721TransferFromRequestBody = { + from: string, to: string, tokenId: string, + waitForReceipt?: boolean +} + +type erc721TransferFromRequestParams = { + chainId: string + contractAddress: string +} + +type erc721TransferFromResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721TransferFromSchema = { + tags: ['erc721'], + body: { + type: 'object', + required: ['from', 'to', 'tokenId'], + properties: { + from: { type: 'string' }, + to: { type: 'string' }, + tokenId: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721TransferFrom(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721TransferFromRequestParams + Body: erc721TransferFromRequestBody + Reply: erc721TransferFromResponse + }>( + '/write/erc721/:chainId/:contractAddress/transferFrom', + { schema: erc721TransferFromSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { from, to, tokenId, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721Abi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('transferFrom', [ + from, to, BigInt(tokenId) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending transferFrom transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'TransferFrom transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721Abi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [from, to, String(tokenId)], + functionName: 'transferFrom' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/index.ts b/src/routes/contract/extensions/erc721Items/index.ts new file mode 100644 index 0000000..27f894d --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/index.ts @@ -0,0 +1,79 @@ +import type { FastifyInstance } from 'fastify' + +import { erc721ItemsApprove } from './write/approve' +import { erc721ItemsBatchBurn } from './write/batchBurn' +import { erc721ItemsBurn } from './write/burn' +import { erc721ItemsGrantRole } from './write/grantRole' +import { erc721ItemsInitialize } from './write/initialize' +import { erc721ItemsMint } from './write/mint' +import { erc721ItemsMintSequential } from './write/mintSequential' +import { erc721ItemsRenounceRole } from './write/renounceRole' +import { erc721ItemsRevokeRole } from './write/revokeRole' +import { erc721ItemsSafeTransferFrom_1 } from './write/safeTransferFrom_1' +import { erc721ItemsSafeTransferFrom_2 } from './write/safeTransferFrom_2' +import { erc721ItemsSetApprovalForAll } from './write/setApprovalForAll' +import { erc721ItemsSetBaseMetadataURI } from './write/setBaseMetadataURI' +import { erc721ItemsSetContractURI } from './write/setContractURI' +import { erc721ItemsSetDefaultRoyalty } from './write/setDefaultRoyalty' +import { erc721ItemsSetImplicitModeProjectId } from './write/setImplicitModeProjectId' +import { erc721ItemsSetImplicitModeValidator } from './write/setImplicitModeValidator' +import { erc721ItemsSetNameAndSymbol } from './write/setNameAndSymbol' +import { erc721ItemsSetTokenRoyalty } from './write/setTokenRoyalty' +import { erc721ItemsTransferFrom } from './write/transferFrom' +import { erc721ItemsDEFAULT_ADMIN_ROLE } from './read/DEFAULT_ADMIN_ROLE' +import { erc721ItemsAcceptImplicitRequest } from './read/acceptImplicitRequest' +import { erc721ItemsBalanceOf } from './read/balanceOf' +import { erc721ItemsContractURI } from './read/contractURI' +import { erc721ItemsGetApproved } from './read/getApproved' +import { erc721ItemsGetRoleAdmin } from './read/getRoleAdmin' +import { erc721ItemsGetRoleMember } from './read/getRoleMember' +import { erc721ItemsGetRoleMemberCount } from './read/getRoleMemberCount' +import { erc721ItemsHasRole } from './read/hasRole' +import { erc721ItemsIsApprovedForAll } from './read/isApprovedForAll' +import { erc721ItemsName } from './read/name' +import { erc721ItemsOwnerOf } from './read/ownerOf' +import { erc721ItemsRoyaltyInfo } from './read/royaltyInfo' +import { erc721ItemsSupportsInterface } from './read/supportsInterface' +import { erc721ItemsSymbol } from './read/symbol' +import { erc721ItemsTokenURI } from './read/tokenURI' +import { erc721ItemsTotalSupply } from './read/totalSupply' + +export function registerErc721ItemsRoutes(fastify: FastifyInstance) { + erc721ItemsApprove(fastify) + erc721ItemsBatchBurn(fastify) + erc721ItemsBurn(fastify) + erc721ItemsGrantRole(fastify) + erc721ItemsInitialize(fastify) + erc721ItemsMint(fastify) + erc721ItemsMintSequential(fastify) + erc721ItemsRenounceRole(fastify) + erc721ItemsRevokeRole(fastify) + erc721ItemsSafeTransferFrom_1(fastify) + erc721ItemsSafeTransferFrom_2(fastify) + erc721ItemsSetApprovalForAll(fastify) + erc721ItemsSetBaseMetadataURI(fastify) + erc721ItemsSetContractURI(fastify) + erc721ItemsSetDefaultRoyalty(fastify) + erc721ItemsSetImplicitModeProjectId(fastify) + erc721ItemsSetImplicitModeValidator(fastify) + erc721ItemsSetNameAndSymbol(fastify) + erc721ItemsSetTokenRoyalty(fastify) + erc721ItemsTransferFrom(fastify) + erc721ItemsDEFAULT_ADMIN_ROLE(fastify) + erc721ItemsAcceptImplicitRequest(fastify) + erc721ItemsBalanceOf(fastify) + erc721ItemsContractURI(fastify) + erc721ItemsGetApproved(fastify) + erc721ItemsGetRoleAdmin(fastify) + erc721ItemsGetRoleMember(fastify) + erc721ItemsGetRoleMemberCount(fastify) + erc721ItemsHasRole(fastify) + erc721ItemsIsApprovedForAll(fastify) + erc721ItemsName(fastify) + erc721ItemsOwnerOf(fastify) + erc721ItemsRoyaltyInfo(fastify) + erc721ItemsSupportsInterface(fastify) + erc721ItemsSymbol(fastify) + erc721ItemsTokenURI(fastify) + erc721ItemsTotalSupply(fastify) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/DEFAULT_ADMIN_ROLE.ts b/src/routes/contract/extensions/erc721Items/read/DEFAULT_ADMIN_ROLE.ts new file mode 100644 index 0000000..85bb1f2 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/DEFAULT_ADMIN_ROLE.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsDEFAULT_ADMIN_ROLERequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsDEFAULT_ADMIN_ROLEResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsDEFAULT_ADMIN_ROLESchema = { + tags: ['erc721Items'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsDEFAULT_ADMIN_ROLE(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsDEFAULT_ADMIN_ROLERequestParams + + Reply: erc721ItemsDEFAULT_ADMIN_ROLEResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/DEFAULT_ADMIN_ROLE', + { schema: erc721ItemsDEFAULT_ADMIN_ROLESchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.DEFAULT_ADMIN_ROLE() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/acceptImplicitRequest.ts b/src/routes/contract/extensions/erc721Items/read/acceptImplicitRequest.ts new file mode 100644 index 0000000..a469b8b --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/acceptImplicitRequest.ts @@ -0,0 +1,122 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsAcceptImplicitRequestRequestQuery = { + wallet: string, attestation: string, call: string +} + +type erc721ItemsAcceptImplicitRequestRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsAcceptImplicitRequestResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsAcceptImplicitRequestSchema = { + tags: ['erc721Items'], + querystring: { + type: 'object', + required: ['wallet', 'attestation', 'call'], + properties: { + wallet: { type: 'string' }, + attestation: { type: 'string' }, + call: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsAcceptImplicitRequest(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsAcceptImplicitRequestRequestParams + Querystring: erc721ItemsAcceptImplicitRequestRequestQuery + Reply: erc721ItemsAcceptImplicitRequestResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/acceptImplicitRequest', + { schema: erc721ItemsAcceptImplicitRequestSchema }, + async (request, reply) => { + const { wallet, attestation, call } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.acceptImplicitRequest(wallet, attestation, call) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/balanceOf.ts b/src/routes/contract/extensions/erc721Items/read/balanceOf.ts new file mode 100644 index 0000000..6017922 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/balanceOf.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsBalanceOfRequestQuery = { + owner: string +} + +type erc721ItemsBalanceOfRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsBalanceOfResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsBalanceOfSchema = { + tags: ['erc721Items'], + querystring: { + type: 'object', + required: ['owner'], + properties: { + owner: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsBalanceOf(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsBalanceOfRequestParams + Querystring: erc721ItemsBalanceOfRequestQuery + Reply: erc721ItemsBalanceOfResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/balanceOf', + { schema: erc721ItemsBalanceOfSchema }, + async (request, reply) => { + const { owner } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.balanceOf(owner) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/contractURI.ts b/src/routes/contract/extensions/erc721Items/read/contractURI.ts new file mode 100644 index 0000000..95a42d7 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/contractURI.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsContractURIRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsContractURIResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsContractURISchema = { + tags: ['erc721Items'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsContractURI(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsContractURIRequestParams + + Reply: erc721ItemsContractURIResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/contractURI', + { schema: erc721ItemsContractURISchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.contractURI() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/getApproved.ts b/src/routes/contract/extensions/erc721Items/read/getApproved.ts new file mode 100644 index 0000000..4f001fe --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/getApproved.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsGetApprovedRequestQuery = { + id: string +} + +type erc721ItemsGetApprovedRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsGetApprovedResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsGetApprovedSchema = { + tags: ['erc721Items'], + querystring: { + type: 'object', + required: ['id'], + properties: { + id: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsGetApproved(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsGetApprovedRequestParams + Querystring: erc721ItemsGetApprovedRequestQuery + Reply: erc721ItemsGetApprovedResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/getApproved', + { schema: erc721ItemsGetApprovedSchema }, + async (request, reply) => { + const { id } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.getApproved(id) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/getRoleAdmin.ts b/src/routes/contract/extensions/erc721Items/read/getRoleAdmin.ts new file mode 100644 index 0000000..948c755 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/getRoleAdmin.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsGetRoleAdminRequestQuery = { + role: string +} + +type erc721ItemsGetRoleAdminRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsGetRoleAdminResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsGetRoleAdminSchema = { + tags: ['erc721Items'], + querystring: { + type: 'object', + required: ['role'], + properties: { + role: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsGetRoleAdmin(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsGetRoleAdminRequestParams + Querystring: erc721ItemsGetRoleAdminRequestQuery + Reply: erc721ItemsGetRoleAdminResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/getRoleAdmin', + { schema: erc721ItemsGetRoleAdminSchema }, + async (request, reply) => { + const { role } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.getRoleAdmin(role) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/getRoleMember.ts b/src/routes/contract/extensions/erc721Items/read/getRoleMember.ts new file mode 100644 index 0000000..45ed051 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/getRoleMember.ts @@ -0,0 +1,121 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsGetRoleMemberRequestQuery = { + role: string, index: string +} + +type erc721ItemsGetRoleMemberRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsGetRoleMemberResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsGetRoleMemberSchema = { + tags: ['erc721Items'], + querystring: { + type: 'object', + required: ['role', 'index'], + properties: { + role: { type: 'string' }, + index: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsGetRoleMember(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsGetRoleMemberRequestParams + Querystring: erc721ItemsGetRoleMemberRequestQuery + Reply: erc721ItemsGetRoleMemberResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/getRoleMember', + { schema: erc721ItemsGetRoleMemberSchema }, + async (request, reply) => { + const { role, index } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.getRoleMember(role, index) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/getRoleMemberCount.ts b/src/routes/contract/extensions/erc721Items/read/getRoleMemberCount.ts new file mode 100644 index 0000000..6cab32f --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/getRoleMemberCount.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsGetRoleMemberCountRequestQuery = { + role: string +} + +type erc721ItemsGetRoleMemberCountRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsGetRoleMemberCountResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsGetRoleMemberCountSchema = { + tags: ['erc721Items'], + querystring: { + type: 'object', + required: ['role'], + properties: { + role: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsGetRoleMemberCount(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsGetRoleMemberCountRequestParams + Querystring: erc721ItemsGetRoleMemberCountRequestQuery + Reply: erc721ItemsGetRoleMemberCountResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/getRoleMemberCount', + { schema: erc721ItemsGetRoleMemberCountSchema }, + async (request, reply) => { + const { role } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.getRoleMemberCount(role) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/hasRole.ts b/src/routes/contract/extensions/erc721Items/read/hasRole.ts new file mode 100644 index 0000000..965ac9c --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/hasRole.ts @@ -0,0 +1,121 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsHasRoleRequestQuery = { + role: string, account: string +} + +type erc721ItemsHasRoleRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsHasRoleResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsHasRoleSchema = { + tags: ['erc721Items'], + querystring: { + type: 'object', + required: ['role', 'account'], + properties: { + role: { type: 'string' }, + account: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsHasRole(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsHasRoleRequestParams + Querystring: erc721ItemsHasRoleRequestQuery + Reply: erc721ItemsHasRoleResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/hasRole', + { schema: erc721ItemsHasRoleSchema }, + async (request, reply) => { + const { role, account } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.hasRole(role, account) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/isApprovedForAll.ts b/src/routes/contract/extensions/erc721Items/read/isApprovedForAll.ts new file mode 100644 index 0000000..2175dae --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/isApprovedForAll.ts @@ -0,0 +1,121 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsIsApprovedForAllRequestQuery = { + owner: string, operator: string +} + +type erc721ItemsIsApprovedForAllRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsIsApprovedForAllResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsIsApprovedForAllSchema = { + tags: ['erc721Items'], + querystring: { + type: 'object', + required: ['owner', 'operator'], + properties: { + owner: { type: 'string' }, + operator: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsIsApprovedForAll(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsIsApprovedForAllRequestParams + Querystring: erc721ItemsIsApprovedForAllRequestQuery + Reply: erc721ItemsIsApprovedForAllResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/isApprovedForAll', + { schema: erc721ItemsIsApprovedForAllSchema }, + async (request, reply) => { + const { owner, operator } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.isApprovedForAll(owner, operator) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/name.ts b/src/routes/contract/extensions/erc721Items/read/name.ts new file mode 100644 index 0000000..fbed808 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/name.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsNameRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsNameResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsNameSchema = { + tags: ['erc721Items'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsName(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsNameRequestParams + + Reply: erc721ItemsNameResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/name', + { schema: erc721ItemsNameSchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.name() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/ownerOf.ts b/src/routes/contract/extensions/erc721Items/read/ownerOf.ts new file mode 100644 index 0000000..1fc95c6 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/ownerOf.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsOwnerOfRequestQuery = { + id: string +} + +type erc721ItemsOwnerOfRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsOwnerOfResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsOwnerOfSchema = { + tags: ['erc721Items'], + querystring: { + type: 'object', + required: ['id'], + properties: { + id: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsOwnerOf(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsOwnerOfRequestParams + Querystring: erc721ItemsOwnerOfRequestQuery + Reply: erc721ItemsOwnerOfResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/ownerOf', + { schema: erc721ItemsOwnerOfSchema }, + async (request, reply) => { + const { id } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.ownerOf(id) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/royaltyInfo.ts b/src/routes/contract/extensions/erc721Items/read/royaltyInfo.ts new file mode 100644 index 0000000..73ae7fe --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/royaltyInfo.ts @@ -0,0 +1,121 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsRoyaltyInfoRequestQuery = { + tokenId: string, salePrice: string +} + +type erc721ItemsRoyaltyInfoRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsRoyaltyInfoResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsRoyaltyInfoSchema = { + tags: ['erc721Items'], + querystring: { + type: 'object', + required: ['tokenId', 'salePrice'], + properties: { + tokenId: { type: 'string' }, + salePrice: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsRoyaltyInfo(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsRoyaltyInfoRequestParams + Querystring: erc721ItemsRoyaltyInfoRequestQuery + Reply: erc721ItemsRoyaltyInfoResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/royaltyInfo', + { schema: erc721ItemsRoyaltyInfoSchema }, + async (request, reply) => { + const { tokenId, salePrice } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.royaltyInfo(tokenId, salePrice) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/supportsInterface.ts b/src/routes/contract/extensions/erc721Items/read/supportsInterface.ts new file mode 100644 index 0000000..e935370 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/supportsInterface.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsSupportsInterfaceRequestQuery = { + interfaceId: string +} + +type erc721ItemsSupportsInterfaceRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsSupportsInterfaceResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsSupportsInterfaceSchema = { + tags: ['erc721Items'], + querystring: { + type: 'object', + required: ['interfaceId'], + properties: { + interfaceId: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsSupportsInterface(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsSupportsInterfaceRequestParams + Querystring: erc721ItemsSupportsInterfaceRequestQuery + Reply: erc721ItemsSupportsInterfaceResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/supportsInterface', + { schema: erc721ItemsSupportsInterfaceSchema }, + async (request, reply) => { + const { interfaceId } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.supportsInterface(interfaceId) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/symbol.ts b/src/routes/contract/extensions/erc721Items/read/symbol.ts new file mode 100644 index 0000000..f73b24c --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/symbol.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsSymbolRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsSymbolResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsSymbolSchema = { + tags: ['erc721Items'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsSymbol(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsSymbolRequestParams + + Reply: erc721ItemsSymbolResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/symbol', + { schema: erc721ItemsSymbolSchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.symbol() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/tokenURI.ts b/src/routes/contract/extensions/erc721Items/read/tokenURI.ts new file mode 100644 index 0000000..6657db9 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/tokenURI.ts @@ -0,0 +1,120 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsTokenURIRequestQuery = { + tokenId: string +} + +type erc721ItemsTokenURIRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsTokenURIResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsTokenURISchema = { + tags: ['erc721Items'], + querystring: { + type: 'object', + required: ['tokenId'], + properties: { + tokenId: { type: 'string' } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsTokenURI(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsTokenURIRequestParams + Querystring: erc721ItemsTokenURIRequestQuery + Reply: erc721ItemsTokenURIResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/tokenURI', + { schema: erc721ItemsTokenURISchema }, + async (request, reply) => { + const { tokenId } = request.query + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.tokenURI(tokenId) + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/read/totalSupply.ts b/src/routes/contract/extensions/erc721Items/read/totalSupply.ts new file mode 100644 index 0000000..32d713d --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/read/totalSupply.ts @@ -0,0 +1,109 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { getSigner } from '~/utils/wallet' + +function serializeBigInt(obj: any): any { + if (obj === null || obj === undefined) { + return obj; + } + + if (typeof obj === 'bigint') { + return obj.toString(); + } + + if (Array.isArray(obj)) { + return obj.map(serializeBigInt); + } + + if (typeof obj === 'object') { + const serialized: any = {}; + for (const [key, value] of Object.entries(obj)) { + serialized[key] = serializeBigInt(value); + } + return serialized; + } + + return obj; +} + +type erc721ItemsTotalSupplyRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsTotalSupplyResponse = { + result?: { + data: any + error?: string + } +} + +const erc721ItemsTotalSupplySchema = { + tags: ['erc721Items'], + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + data: {}, + error: { type: 'string', nullable: true } + } + } + } + } + } +} + +export async function erc721ItemsTotalSupply(fastify: FastifyInstance) { + fastify.get<{ + Params: erc721ItemsTotalSupplyRequestParams + + Reply: erc721ItemsTotalSupplyResponse + }>( + '/read/erc721Items/:chainId/:contractAddress/totalSupply', + { schema: erc721ItemsTotalSupplySchema }, + async (request, reply) => { + + const { chainId, contractAddress } = request.params + + try { + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + throw new Error('Signer not configured correctly.') + } + + const contract = new ethers.Contract( + contractAddress, + erc721ItemsAbi, + signer + ) + + const result = await contract.totalSupply() + + return reply.code(200).send({ + result: { + data: serializeBigInt(result) + } + }) + } catch (error) { + return reply.code(500).send({ + result: { + data: null, + error: error instanceof Error ? error.message : 'Read failed' + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/approve.ts b/src/routes/contract/extensions/erc721Items/write/approve.ts new file mode 100644 index 0000000..55b0c47 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/approve.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsApproveRequestBody = { + account: string, id: string, + waitForReceipt?: boolean +} + +type erc721ItemsApproveRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsApproveResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsApproveSchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['account', 'id'], + properties: { + account: { type: 'string' }, + id: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsApprove(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsApproveRequestParams + Body: erc721ItemsApproveRequestBody + Reply: erc721ItemsApproveResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/approve', + { schema: erc721ItemsApproveSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { account, id, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('approve', [ + account, BigInt(id) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending approve transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'Approve transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [account, String(id)], + functionName: 'approve' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/batchBurn.ts b/src/routes/contract/extensions/erc721Items/write/batchBurn.ts new file mode 100644 index 0000000..1f587c5 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/batchBurn.ts @@ -0,0 +1,187 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsBatchBurnRequestBody = { + tokenIds: string[], + waitForReceipt?: boolean +} + +type erc721ItemsBatchBurnRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsBatchBurnResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsBatchBurnSchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['tokenIds'], + properties: { + tokenIds: { type: 'array', items: { type: 'string' } }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsBatchBurn(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsBatchBurnRequestParams + Body: erc721ItemsBatchBurnRequestBody + Reply: erc721ItemsBatchBurnResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/batchBurn', + { schema: erc721ItemsBatchBurnSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { tokenIds, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('batchBurn', [ + tokenIds.map((v) => BigInt(v)) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending batchBurn transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'BatchBurn transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [tokenIds.map(String)], + functionName: 'batchBurn' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/burn.ts b/src/routes/contract/extensions/erc721Items/write/burn.ts new file mode 100644 index 0000000..0a48f90 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/burn.ts @@ -0,0 +1,187 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsBurnRequestBody = { + tokenId: string, + waitForReceipt?: boolean +} + +type erc721ItemsBurnRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsBurnResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsBurnSchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['tokenId'], + properties: { + tokenId: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsBurn(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsBurnRequestParams + Body: erc721ItemsBurnRequestBody + Reply: erc721ItemsBurnResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/burn', + { schema: erc721ItemsBurnSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { tokenId, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('burn', [ + BigInt(tokenId) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending burn transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'Burn transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [String(tokenId)], + functionName: 'burn' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/grantRole.ts b/src/routes/contract/extensions/erc721Items/write/grantRole.ts new file mode 100644 index 0000000..262ad24 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/grantRole.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsGrantRoleRequestBody = { + role: string, account: string, + waitForReceipt?: boolean +} + +type erc721ItemsGrantRoleRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsGrantRoleResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsGrantRoleSchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['role', 'account'], + properties: { + role: { type: 'string' }, + account: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsGrantRole(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsGrantRoleRequestParams + Body: erc721ItemsGrantRoleRequestBody + Reply: erc721ItemsGrantRoleResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/grantRole', + { schema: erc721ItemsGrantRoleSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { role, account, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('grantRole', [ + role, account + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending grantRole transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'GrantRole transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [role, account], + functionName: 'grantRole' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/initialize.ts b/src/routes/contract/extensions/erc721Items/write/initialize.ts new file mode 100644 index 0000000..ea62c3b --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/initialize.ts @@ -0,0 +1,195 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsInitializeRequestBody = { + owner: string, tokenName: string, tokenSymbol: string, tokenBaseURI: string, tokenContractURI: string, royaltyReceiver: string, royaltyFeeNumerator: string, implicitModeValidator: string, implicitModeProjectId: string, + waitForReceipt?: boolean +} + +type erc721ItemsInitializeRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsInitializeResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsInitializeSchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['owner', 'tokenName', 'tokenSymbol', 'tokenBaseURI', 'tokenContractURI', 'royaltyReceiver', 'royaltyFeeNumerator', 'implicitModeValidator', 'implicitModeProjectId'], + properties: { + owner: { type: 'string' }, + tokenName: { type: 'string' }, + tokenSymbol: { type: 'string' }, + tokenBaseURI: { type: 'string' }, + tokenContractURI: { type: 'string' }, + royaltyReceiver: { type: 'string' }, + royaltyFeeNumerator: { type: 'string' }, + implicitModeValidator: { type: 'string' }, + implicitModeProjectId: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsInitialize(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsInitializeRequestParams + Body: erc721ItemsInitializeRequestBody + Reply: erc721ItemsInitializeResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/initialize', + { schema: erc721ItemsInitializeSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { owner, tokenName, tokenSymbol, tokenBaseURI, tokenContractURI, royaltyReceiver, royaltyFeeNumerator, implicitModeValidator, implicitModeProjectId, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('initialize', [ + owner, tokenName, tokenSymbol, tokenBaseURI, tokenContractURI, royaltyReceiver, BigInt(royaltyFeeNumerator), implicitModeValidator, implicitModeProjectId + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending initialize transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'Initialize transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [owner, tokenName, tokenSymbol, tokenBaseURI, tokenContractURI, royaltyReceiver, String(royaltyFeeNumerator), implicitModeValidator, implicitModeProjectId], + functionName: 'initialize' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/mint.ts b/src/routes/contract/extensions/erc721Items/write/mint.ts new file mode 100644 index 0000000..9fc9124 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/mint.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsMintRequestBody = { + to: string, tokenId: string, + waitForReceipt?: boolean +} + +type erc721ItemsMintRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsMintResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsMintSchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['to', 'tokenId'], + properties: { + to: { type: 'string' }, + tokenId: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsMint(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsMintRequestParams + Body: erc721ItemsMintRequestBody + Reply: erc721ItemsMintResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/mint', + { schema: erc721ItemsMintSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { to, tokenId, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('mint', [ + to, BigInt(tokenId) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending mint transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'Mint transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [to, String(tokenId)], + functionName: 'mint' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/mintSequential.ts b/src/routes/contract/extensions/erc721Items/write/mintSequential.ts new file mode 100644 index 0000000..d3fb4e1 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/mintSequential.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsMintSequentialRequestBody = { + to: string, amount: string, + waitForReceipt?: boolean +} + +type erc721ItemsMintSequentialRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsMintSequentialResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsMintSequentialSchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['to', 'amount'], + properties: { + to: { type: 'string' }, + amount: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsMintSequential(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsMintSequentialRequestParams + Body: erc721ItemsMintSequentialRequestBody + Reply: erc721ItemsMintSequentialResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/mintSequential', + { schema: erc721ItemsMintSequentialSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { to, amount, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('mintSequential', [ + to, BigInt(amount) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending mintSequential transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'MintSequential transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [to, String(amount)], + functionName: 'mintSequential' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/renounceRole.ts b/src/routes/contract/extensions/erc721Items/write/renounceRole.ts new file mode 100644 index 0000000..0380932 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/renounceRole.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsRenounceRoleRequestBody = { + role: string, account: string, + waitForReceipt?: boolean +} + +type erc721ItemsRenounceRoleRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsRenounceRoleResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsRenounceRoleSchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['role', 'account'], + properties: { + role: { type: 'string' }, + account: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsRenounceRole(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsRenounceRoleRequestParams + Body: erc721ItemsRenounceRoleRequestBody + Reply: erc721ItemsRenounceRoleResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/renounceRole', + { schema: erc721ItemsRenounceRoleSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { role, account, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('renounceRole', [ + role, account + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending renounceRole transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'RenounceRole transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [role, account], + functionName: 'renounceRole' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/revokeRole.ts b/src/routes/contract/extensions/erc721Items/write/revokeRole.ts new file mode 100644 index 0000000..fc9670b --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/revokeRole.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsRevokeRoleRequestBody = { + role: string, account: string, + waitForReceipt?: boolean +} + +type erc721ItemsRevokeRoleRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsRevokeRoleResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsRevokeRoleSchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['role', 'account'], + properties: { + role: { type: 'string' }, + account: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsRevokeRole(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsRevokeRoleRequestParams + Body: erc721ItemsRevokeRoleRequestBody + Reply: erc721ItemsRevokeRoleResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/revokeRole', + { schema: erc721ItemsRevokeRoleSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { role, account, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('revokeRole', [ + role, account + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending revokeRole transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'RevokeRole transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [role, account], + functionName: 'revokeRole' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/safeTransferFrom_1.ts b/src/routes/contract/extensions/erc721Items/write/safeTransferFrom_1.ts new file mode 100644 index 0000000..14bc929 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/safeTransferFrom_1.ts @@ -0,0 +1,189 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsSafeTransferFrom_1RequestBody = { + from: string, to: string, id: string, + waitForReceipt?: boolean +} + +type erc721ItemsSafeTransferFrom_1RequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsSafeTransferFrom_1Response = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsSafeTransferFrom_1Schema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['from', 'to', 'id'], + properties: { + from: { type: 'string' }, + to: { type: 'string' }, + id: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsSafeTransferFrom_1(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsSafeTransferFrom_1RequestParams + Body: erc721ItemsSafeTransferFrom_1RequestBody + Reply: erc721ItemsSafeTransferFrom_1Response + }>( + '/write/erc721Items/:chainId/:contractAddress/safeTransferFrom_1', + { schema: erc721ItemsSafeTransferFrom_1Schema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { from, to, id, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('safeTransferFrom', [ + from, to, BigInt(id) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending safeTransferFrom transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SafeTransferFrom transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [from, to, String(id)], + functionName: 'safeTransferFrom' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/safeTransferFrom_2.ts b/src/routes/contract/extensions/erc721Items/write/safeTransferFrom_2.ts new file mode 100644 index 0000000..f0b28b6 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/safeTransferFrom_2.ts @@ -0,0 +1,190 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsSafeTransferFrom_2RequestBody = { + from: string, to: string, id: string, data: string, + waitForReceipt?: boolean +} + +type erc721ItemsSafeTransferFrom_2RequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsSafeTransferFrom_2Response = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsSafeTransferFrom_2Schema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['from', 'to', 'id', 'data'], + properties: { + from: { type: 'string' }, + to: { type: 'string' }, + id: { type: 'string' }, + data: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsSafeTransferFrom_2(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsSafeTransferFrom_2RequestParams + Body: erc721ItemsSafeTransferFrom_2RequestBody + Reply: erc721ItemsSafeTransferFrom_2Response + }>( + '/write/erc721Items/:chainId/:contractAddress/safeTransferFrom_2', + { schema: erc721ItemsSafeTransferFrom_2Schema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { from, to, id, data, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('safeTransferFrom', [ + from, to, BigInt(id), data + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending safeTransferFrom transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SafeTransferFrom transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [from, to, String(id), data], + functionName: 'safeTransferFrom' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/setApprovalForAll.ts b/src/routes/contract/extensions/erc721Items/write/setApprovalForAll.ts new file mode 100644 index 0000000..82fec70 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/setApprovalForAll.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsSetApprovalForAllRequestBody = { + operator: string, isApproved: string, + waitForReceipt?: boolean +} + +type erc721ItemsSetApprovalForAllRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsSetApprovalForAllResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsSetApprovalForAllSchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['operator', 'isApproved'], + properties: { + operator: { type: 'string' }, + isApproved: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsSetApprovalForAll(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsSetApprovalForAllRequestParams + Body: erc721ItemsSetApprovalForAllRequestBody + Reply: erc721ItemsSetApprovalForAllResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/setApprovalForAll', + { schema: erc721ItemsSetApprovalForAllSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { operator, isApproved, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setApprovalForAll', [ + operator, isApproved + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setApprovalForAll transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetApprovalForAll transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [operator, isApproved], + functionName: 'setApprovalForAll' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/setBaseMetadataURI.ts b/src/routes/contract/extensions/erc721Items/write/setBaseMetadataURI.ts new file mode 100644 index 0000000..493d151 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/setBaseMetadataURI.ts @@ -0,0 +1,187 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsSetBaseMetadataURIRequestBody = { + tokenBaseURI: string, + waitForReceipt?: boolean +} + +type erc721ItemsSetBaseMetadataURIRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsSetBaseMetadataURIResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsSetBaseMetadataURISchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['tokenBaseURI'], + properties: { + tokenBaseURI: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsSetBaseMetadataURI(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsSetBaseMetadataURIRequestParams + Body: erc721ItemsSetBaseMetadataURIRequestBody + Reply: erc721ItemsSetBaseMetadataURIResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/setBaseMetadataURI', + { schema: erc721ItemsSetBaseMetadataURISchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { tokenBaseURI, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setBaseMetadataURI', [ + tokenBaseURI + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setBaseMetadataURI transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetBaseMetadataURI transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [tokenBaseURI], + functionName: 'setBaseMetadataURI' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/setContractURI.ts b/src/routes/contract/extensions/erc721Items/write/setContractURI.ts new file mode 100644 index 0000000..66c4946 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/setContractURI.ts @@ -0,0 +1,187 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsSetContractURIRequestBody = { + tokenContractURI: string, + waitForReceipt?: boolean +} + +type erc721ItemsSetContractURIRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsSetContractURIResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsSetContractURISchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['tokenContractURI'], + properties: { + tokenContractURI: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsSetContractURI(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsSetContractURIRequestParams + Body: erc721ItemsSetContractURIRequestBody + Reply: erc721ItemsSetContractURIResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/setContractURI', + { schema: erc721ItemsSetContractURISchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { tokenContractURI, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setContractURI', [ + tokenContractURI + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setContractURI transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetContractURI transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [tokenContractURI], + functionName: 'setContractURI' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/setDefaultRoyalty.ts b/src/routes/contract/extensions/erc721Items/write/setDefaultRoyalty.ts new file mode 100644 index 0000000..abe372e --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/setDefaultRoyalty.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsSetDefaultRoyaltyRequestBody = { + receiver: string, feeNumerator: string, + waitForReceipt?: boolean +} + +type erc721ItemsSetDefaultRoyaltyRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsSetDefaultRoyaltyResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsSetDefaultRoyaltySchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['receiver', 'feeNumerator'], + properties: { + receiver: { type: 'string' }, + feeNumerator: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsSetDefaultRoyalty(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsSetDefaultRoyaltyRequestParams + Body: erc721ItemsSetDefaultRoyaltyRequestBody + Reply: erc721ItemsSetDefaultRoyaltyResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/setDefaultRoyalty', + { schema: erc721ItemsSetDefaultRoyaltySchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { receiver, feeNumerator, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setDefaultRoyalty', [ + receiver, BigInt(feeNumerator) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setDefaultRoyalty transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetDefaultRoyalty transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [receiver, String(feeNumerator)], + functionName: 'setDefaultRoyalty' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/setImplicitModeProjectId.ts b/src/routes/contract/extensions/erc721Items/write/setImplicitModeProjectId.ts new file mode 100644 index 0000000..8184b7f --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/setImplicitModeProjectId.ts @@ -0,0 +1,187 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsSetImplicitModeProjectIdRequestBody = { + projectId: string, + waitForReceipt?: boolean +} + +type erc721ItemsSetImplicitModeProjectIdRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsSetImplicitModeProjectIdResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsSetImplicitModeProjectIdSchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['projectId'], + properties: { + projectId: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsSetImplicitModeProjectId(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsSetImplicitModeProjectIdRequestParams + Body: erc721ItemsSetImplicitModeProjectIdRequestBody + Reply: erc721ItemsSetImplicitModeProjectIdResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/setImplicitModeProjectId', + { schema: erc721ItemsSetImplicitModeProjectIdSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { projectId, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setImplicitModeProjectId', [ + projectId + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setImplicitModeProjectId transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetImplicitModeProjectId transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [projectId], + functionName: 'setImplicitModeProjectId' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/setImplicitModeValidator.ts b/src/routes/contract/extensions/erc721Items/write/setImplicitModeValidator.ts new file mode 100644 index 0000000..0d72856 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/setImplicitModeValidator.ts @@ -0,0 +1,187 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsSetImplicitModeValidatorRequestBody = { + validator: string, + waitForReceipt?: boolean +} + +type erc721ItemsSetImplicitModeValidatorRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsSetImplicitModeValidatorResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsSetImplicitModeValidatorSchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['validator'], + properties: { + validator: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsSetImplicitModeValidator(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsSetImplicitModeValidatorRequestParams + Body: erc721ItemsSetImplicitModeValidatorRequestBody + Reply: erc721ItemsSetImplicitModeValidatorResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/setImplicitModeValidator', + { schema: erc721ItemsSetImplicitModeValidatorSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { validator, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setImplicitModeValidator', [ + validator + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setImplicitModeValidator transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetImplicitModeValidator transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [validator], + functionName: 'setImplicitModeValidator' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/setNameAndSymbol.ts b/src/routes/contract/extensions/erc721Items/write/setNameAndSymbol.ts new file mode 100644 index 0000000..994dfa6 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/setNameAndSymbol.ts @@ -0,0 +1,188 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsSetNameAndSymbolRequestBody = { + tokenName: string, tokenSymbol: string, + waitForReceipt?: boolean +} + +type erc721ItemsSetNameAndSymbolRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsSetNameAndSymbolResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsSetNameAndSymbolSchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['tokenName', 'tokenSymbol'], + properties: { + tokenName: { type: 'string' }, + tokenSymbol: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsSetNameAndSymbol(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsSetNameAndSymbolRequestParams + Body: erc721ItemsSetNameAndSymbolRequestBody + Reply: erc721ItemsSetNameAndSymbolResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/setNameAndSymbol', + { schema: erc721ItemsSetNameAndSymbolSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { tokenName, tokenSymbol, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setNameAndSymbol', [ + tokenName, tokenSymbol + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setNameAndSymbol transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetNameAndSymbol transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [tokenName, tokenSymbol], + functionName: 'setNameAndSymbol' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/setTokenRoyalty.ts b/src/routes/contract/extensions/erc721Items/write/setTokenRoyalty.ts new file mode 100644 index 0000000..429d100 --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/setTokenRoyalty.ts @@ -0,0 +1,189 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsSetTokenRoyaltyRequestBody = { + tokenId: string, receiver: string, feeNumerator: string, + waitForReceipt?: boolean +} + +type erc721ItemsSetTokenRoyaltyRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsSetTokenRoyaltyResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsSetTokenRoyaltySchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['tokenId', 'receiver', 'feeNumerator'], + properties: { + tokenId: { type: 'string' }, + receiver: { type: 'string' }, + feeNumerator: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsSetTokenRoyalty(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsSetTokenRoyaltyRequestParams + Body: erc721ItemsSetTokenRoyaltyRequestBody + Reply: erc721ItemsSetTokenRoyaltyResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/setTokenRoyalty', + { schema: erc721ItemsSetTokenRoyaltySchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { tokenId, receiver, feeNumerator, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('setTokenRoyalty', [ + BigInt(tokenId), receiver, BigInt(feeNumerator) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending setTokenRoyalty transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'SetTokenRoyalty transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [String(tokenId), receiver, String(feeNumerator)], + functionName: 'setTokenRoyalty' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/extensions/erc721Items/write/transferFrom.ts b/src/routes/contract/extensions/erc721Items/write/transferFrom.ts new file mode 100644 index 0000000..d4f4b6b --- /dev/null +++ b/src/routes/contract/extensions/erc721Items/write/transferFrom.ts @@ -0,0 +1,189 @@ +import type { FastifyInstance } from 'fastify' +import { ethers } from 'ethers' +import { erc721ItemsAbi } from '~/constants/abis/erc721Items' +import { + getTenderlySimulationUrl, + prepareTransactionsForTenderlySimulation +} from '~/routes/contract/utils/tenderly/getSimulationUrl' +import { TransactionService } from '~/services/transaction.service' +import type { TransactionResponse } from '~/types/general' +import { logError, logRequest, logStep } from '~/utils/loggingUtils' +import { extractTxHashFromErrorReceipt, getBlockExplorerUrl } from '~/utils/other' +import { getSigner } from '~/utils/wallet' + +type erc721ItemsTransferFromRequestBody = { + from: string, to: string, id: string, + waitForReceipt?: boolean +} + +type erc721ItemsTransferFromRequestParams = { + chainId: string + contractAddress: string +} + +type erc721ItemsTransferFromResponse = { + result?: { + txHash: string | null + txUrl: string | null + txSimulationUrl?: string | null + error?: string + } +} + +const erc721ItemsTransferFromSchema = { + tags: ['erc721Items'], + body: { + type: 'object', + required: ['from', 'to', 'id'], + properties: { + from: { type: 'string' }, + to: { type: 'string' }, + id: { type: 'string' }, + waitForReceipt: { type: 'boolean', nullable: true } + } + }, + params: { + type: 'object', + required: ['chainId', 'contractAddress'], + properties: { + chainId: { type: 'string' }, + contractAddress: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + 'x-secret-key': { type: 'string', nullable: true } + } + }, + response: { + 200: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string' }, + txUrl: { type: 'string' }, + error: { type: 'string', nullable: true } + } + } + } + }, + 500: { + type: 'object', + properties: { + result: { + type: 'object', + properties: { + txHash: { type: 'string', nullable: true }, + txUrl: { type: 'string', nullable: true }, + txSimulationUrl: { type: 'string', nullable: true }, + error: { type: 'string' } + } + } + } + } + } +} + +export async function erc721ItemsTransferFrom(fastify: FastifyInstance) { + fastify.post<{ + Params: erc721ItemsTransferFromRequestParams + Body: erc721ItemsTransferFromRequestBody + Reply: erc721ItemsTransferFromResponse + }>( + '/write/erc721Items/:chainId/:contractAddress/transferFrom', + { schema: erc721ItemsTransferFromSchema }, + async (request, reply) => { + logRequest(request) + + let tenderlyUrl: string | null = null + let txHash: string | null = null + const { chainId, contractAddress } = request.params + + try { + const { from, to, id, waitForReceipt } = request.body + + const signer = await getSigner(chainId) + if (!signer || !signer.account?.address) { + logError(request, new Error('Signer not configured correctly.'), { signer }) + throw new Error('Signer not configured correctly.') + } + logStep(request, 'Tx signer received', { signer: signer.account.address }) + + const contract = new ethers.Contract(contractAddress, erc721ItemsAbi, signer) + logStep(request, 'Contract instance created') + + const encodedData = contract.interface.encodeFunctionData('transferFrom', [ + from, to, BigInt(id) + ]) + logStep(request, 'Function data encoded') + + const tx = { to: contractAddress, data: encodedData } + + const { simulationData, signedTx } = await prepareTransactionsForTenderlySimulation( + signer, + [tx], + Number(chainId) + ) + tenderlyUrl = getTenderlySimulationUrl({ + chainId: chainId, + gas: 3000000, + block: await signer.provider.getBlockNumber(), + blockIndex: 0, + contractAddress: signedTx.entrypoint, + rawFunctionInput: simulationData + }) + + const txService = new TransactionService(fastify) + + logStep(request, 'Sending transferFrom transaction...') + const txResponse: TransactionResponse = await signer.sendTransaction( + tx, + { waitForReceipt: waitForReceipt ?? false } + ) + txHash = txResponse.hash + logStep(request, 'TransferFrom transaction sent', { txHash: txResponse.hash }) + + if (txResponse.receipt?.status === 0) { + throw new Error('Transaction reverted', { cause: txResponse.receipt }) + } + + await txService.createTransaction({ + chainId, + contractAddress, + abi: erc721ItemsAbi, + data: encodedData, + txHash: txHash, + isDeployTx: false, + args: [from, to, String(id)], + functionName: 'transferFrom' + }) + + return reply.code(200).send({ + result: { + txHash: txHash, + txUrl: getBlockExplorerUrl(Number(chainId), txHash), + txSimulationUrl: tenderlyUrl ?? null + } + }) + } catch (error) { + const errorTxHash = extractTxHashFromErrorReceipt(error) + const finalTxHash = txHash ?? errorTxHash + + logError(request, error, { params: request.params, body: request.body, txHash: finalTxHash }) + + const errorMessage = error instanceof Error ? error.message : 'Failed to execute transaction' + return reply.code(500).send({ + result: { + txHash: finalTxHash, + txUrl: finalTxHash ? getBlockExplorerUrl(Number(chainId), finalTxHash) : null, + txSimulationUrl: tenderlyUrl ?? null, + error: errorMessage + } + }) + } + } + ) +} \ No newline at end of file diff --git a/src/routes/contract/simulate/simulateDeployment.ts b/src/routes/contract/simulate/simulateDeployment.ts index e799a8b..2000cbf 100644 --- a/src/routes/contract/simulate/simulateDeployment.ts +++ b/src/routes/contract/simulate/simulateDeployment.ts @@ -29,7 +29,7 @@ type SimulateDeploymentResponse = { contracts: unknown generated_access_list: unknown error?: string - tenderlySimulationUrl?: string + tenderlySimulationUrl?: string | null } } diff --git a/src/routes/contract/simulate/simulateTransaction.ts b/src/routes/contract/simulate/simulateTransaction.ts index 4ba6884..f00a0a2 100644 --- a/src/routes/contract/simulate/simulateTransaction.ts +++ b/src/routes/contract/simulate/simulateTransaction.ts @@ -31,7 +31,7 @@ type SimulateTransactionResponse = { contracts: unknown generated_access_list: unknown error?: string - tenderlySimulationUrl?: string + tenderlySimulationUrl?: string | null } } @@ -220,7 +220,7 @@ export async function simulateTransaction(fastify: FastifyInstance) { simulation: data.simulation, contracts: data.contracts, generated_access_list: data.generated_access_list, - tenderlySimulationUrl: tenderlyUrl + tenderlySimulationUrl: tenderlyUrl } }) } catch (error) { diff --git a/src/routes/contract/utils/tenderly/getSimulationUrl.ts b/src/routes/contract/utils/tenderly/getSimulationUrl.ts index dc9c0a7..ab471d8 100644 --- a/src/routes/contract/utils/tenderly/getSimulationUrl.ts +++ b/src/routes/contract/utils/tenderly/getSimulationUrl.ts @@ -17,10 +17,14 @@ export const getTenderlySimulationUrl = ({ contractFunction, rawFunctionInput, functionInputs -}: TenderlySimulatorUrlOptions): string => { +}: TenderlySimulatorUrlOptions): string | null => { const accountSlug = process.env.TENDERLY_ACCOUNT_SLUG as string const projectSlug = process.env.TENDERLY_PROJECT_SLUG as string + if (!accountSlug || !projectSlug) { + return null; + } + const baseUrl = `https://dashboard.tenderly.co/${accountSlug}/${projectSlug}/simulator/new` const params = new URLSearchParams({ network: chainId.toString(), diff --git a/src/routes/index.ts b/src/routes/index.ts index 66222e6..a072665 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,6 +1,7 @@ import { ChainId } from '@0xsequence/network' import type { FastifyInstance } from 'fastify' import metrics from '../plugins/metrics/metrics' + import { getSigner } from '../utils/wallet' import { deployContract } from './contract/deploy/contract' import { erc20Deploy } from './contract/deploy/erc20' @@ -9,28 +10,6 @@ import { erc721ItemsDeployAndInitialize } from './contract/deploy/erc721Items' import { erc1155Deploy } from './contract/deploy/erc1155' import { erc1155ItemsDeploy } from './contract/deploy/erc1155Items' import { deployUpgradeableContract } from './contract/deploy/upgradeableContract' -import { erc20Approve } from './contract/extensions/erc20/write/approve' -import { erc20Mint } from './contract/extensions/erc20/write/mint' -import { erc20Transfer } from './contract/extensions/erc20/write/transfer' -import { erc20TransferFrom } from './contract/extensions/erc20/write/transferFrom' -import { erc721ItemsBatchBurn } from './contract/extensions/erc721/erc721Items/write/batchBurn' -import { erc721ItemsBurn } from './contract/extensions/erc721/erc721Items/write/burn' -import { erc721ItemsInitialize } from './contract/extensions/erc721/erc721Items/write/initialize' -import { erc721ItemsMint } from './contract/extensions/erc721/erc721Items/write/mint' -import { erc721BalanceOf } from './contract/extensions/erc721/read/balanceOf' -import { erc721Burn } from './contract/extensions/erc721/write/burn' -import { erc721SafeMint } from './contract/extensions/erc721/write/safeMint' -import { erc721SafeMintBatch } from './contract/extensions/erc721/write/safeMintBatch' -import { erc1155ItemsBatchBurn } from './contract/extensions/erc1155/erc1155Items/write/batchBurn' -import { erc1155ItemsBurn } from './contract/extensions/erc1155/erc1155Items/write/burn' -import { erc1155ItemsInitialize } from './contract/extensions/erc1155/erc1155Items/write/initialize' -import { erc1155ItemsMint } from './contract/extensions/erc1155/erc1155Items/write/mint' -import { erc1155BalanceOf } from './contract/extensions/erc1155/read/balanceOf' -import { erc1155HasRole } from './contract/extensions/erc1155/read/hasRole' -import { erc1155MinterRole } from './contract/extensions/erc1155/read/minterRole' -import { erc1155GrantRole } from './contract/extensions/erc1155/write/grantRole' -import { erc1155Mint } from './contract/extensions/erc1155/write/mint' -import { erc1155MintBatch } from './contract/extensions/erc1155/write/mintBatch' import { readContract } from './contract/read/read' import { simulateDeployment } from './contract/simulate/simulateDeployment' import { simulateTransaction } from './contract/simulate/simulateTransaction' @@ -56,6 +35,11 @@ import { addWebhook } from './webhooks/addWebhook' import { getAllWebhooks } from './webhooks/getAllWebhooks' import { removeAllWebhooks } from './webhooks/removeAllWebhooks' import { removeWebhook } from './webhooks/removeWebhook' +import { registerErc721ItemsRoutes } from './contract/extensions/erc721Items' +import { registerErc1155ItemsRoutes } from './contract/extensions/erc1155Items' +import { registerErc20Routes } from './contract/extensions/erc20' +import { registerErc721Routes } from './contract/extensions/erc721' +import { registerErc1155Routes } from './contract/extensions/erc1155' export default async function (fastify: FastifyInstance) { // Health check route @@ -101,38 +85,6 @@ export default async function (fastify: FastifyInstance) { getAllContracts(fastify) getContract(fastify) - // Register erc20 routes - erc20Transfer(fastify) - erc20Approve(fastify) - erc20Mint(fastify) - erc20TransferFrom(fastify) - - // Register erc721 routes - erc721SafeMint(fastify) - erc721SafeMintBatch(fastify) - erc721BalanceOf(fastify) - erc721Burn(fastify) - - // Register erc1155 routes - erc1155Mint(fastify) - erc1155MintBatch(fastify) - erc1155GrantRole(fastify) - erc1155HasRole(fastify) - erc1155MinterRole(fastify) - erc1155BalanceOf(fastify) - - // Register erc721Items routes - erc721ItemsMint(fastify) - erc721ItemsBurn(fastify) - erc721ItemsBatchBurn(fastify) - erc721ItemsInitialize(fastify) - - // Register erc1155Items routes - erc1155ItemsMint(fastify) - erc1155ItemsBurn(fastify) - erc1155ItemsBatchBurn(fastify) - erc1155ItemsInitialize(fastify) - // Register is deployed route isDeployed(fastify) @@ -182,4 +134,10 @@ export default async function (fastify: FastifyInstance) { // Contract verification verifyContract(fastify) -} + + registerErc721ItemsRoutes(fastify) + registerErc1155ItemsRoutes(fastify) + registerErc20Routes(fastify) + registerErc721Routes(fastify) + registerErc1155Routes(fastify) +} \ No newline at end of file diff --git a/src/services/transaction.service.ts b/src/services/transaction.service.ts index ae87ee4..e90535a 100644 --- a/src/services/transaction.service.ts +++ b/src/services/transaction.service.ts @@ -15,7 +15,7 @@ export class TransactionService { txHash?: string | undefined data?: string | undefined functionName?: string | undefined - args?: Array | undefined + args?: Array | undefined isDeployTx?: boolean }): Promise { if (!process.env.DATABASE_URL) {