From 78f7f35cb86cec921b13d006c8a314530a09d55e Mon Sep 17 00:00:00 2001 From: Martynas Kazlauskas Date: Thu, 8 Feb 2024 15:47:06 +0200 Subject: [PATCH 1/2] fix(core): update isValidHandle RegExp to match ADA Handle rules - validate length - do not allow comma as invalid handle - allow '0|0' as an exception since it exists on mainnet also add whitespace cases in for handle tests and add doc comment and update local network handles to be minted as lowercase --- .../Asset/TypeOrmNftMetadataService.test.ts | 11 ++++---- .../TypeormAssetProvider.test.ts | 4 +-- .../test/jest-setup/snapshots/asset.sql | 12 ++++---- .../test/jest-setup/snapshots/db_sync.sql | 8 +++--- .../test/jest-setup/snapshots/handle.sql | 28 +++++++++---------- packages/core/src/Asset/util/isValidHandle.ts | 26 +++++++++++++++-- .../test/Asset/util/isValidHandle.test.ts | 25 +++++++++++++---- .../e2e/local-network/scripts/mint-handles.sh | 4 +-- .../e2e/test/providers/HandleProvider.test.ts | 6 ++-- 9 files changed, 80 insertions(+), 44 deletions(-) diff --git a/packages/cardano-services/test/Asset/TypeOrmNftMetadataService.test.ts b/packages/cardano-services/test/Asset/TypeOrmNftMetadataService.test.ts index 5cfae5288da..f921fef22f6 100644 --- a/packages/cardano-services/test/Asset/TypeOrmNftMetadataService.test.ts +++ b/packages/cardano-services/test/Asset/TypeOrmNftMetadataService.test.ts @@ -1,4 +1,4 @@ -import { Cardano } from '@cardano-sdk/core'; +import { Cardano, util } from '@cardano-sdk/core'; import { TypeOrmNftMetadataService, createDnsResolver, getConnectionConfig, getEntities } from '../../src'; import { logger, mockProviders } from '@cardano-sdk/util-dev'; @@ -31,11 +31,12 @@ describe('TypeOrmNftMetadataService', () => { }); describe('existing nft', () => { - const helloHandleAssetId = Cardano.AssetId( - '62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a48656c6c6f48616e646c65' + const helloHandleAssetId = Cardano.AssetId.fromParts( + Cardano.PolicyId('62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a'), + Cardano.AssetName(util.utf8ToHex('hellohandle')) ); const testHandleAssetId = Cardano.AssetId( - '62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a5465737448616e646c65' + '62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a7465737468616e646c65' ); const helloHandleOtherProperties = new Map | Map>([ @@ -62,7 +63,7 @@ describe('TypeOrmNftMetadataService', () => { const response = { description: 'The Handle Standard', image: 'ipfs://some-hash', - name: 'HelloHandle', + name: 'hellohandle', otherProperties: helloHandleOtherProperties }; diff --git a/packages/cardano-services/test/Asset/TypeormAssetProvider/TypeormAssetProvider.test.ts b/packages/cardano-services/test/Asset/TypeormAssetProvider/TypeormAssetProvider.test.ts index 28f13247f14..c2c307475eb 100644 --- a/packages/cardano-services/test/Asset/TypeormAssetProvider/TypeormAssetProvider.test.ts +++ b/packages/cardano-services/test/Asset/TypeormAssetProvider/TypeormAssetProvider.test.ts @@ -69,7 +69,7 @@ describe('TypeormAssetProvider', () => { }); it('should throw an error if the asset does not exist', async () => { - const assetId = Cardano.AssetId('64190c10b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a48656c6c6f48616e646c65'); + const assetId = Cardano.AssetId('64190c10b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68656c6c6f68616e646c65'); await expect(provider.getAsset({ assetId })).rejects.toThrowError(ProviderError); }); @@ -124,7 +124,7 @@ describe('TypeormAssetProvider', () => { it('Should throw error when one of the assetIds does not exist', async () => { const testAssets = await fixtureBuilder.getAssets(1); const invalidAssetId = Cardano.AssetId( - '64190c10b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a48656c6c6f48616e646c65' + '64190c10b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68656c6c6f68616e646c65' ); const assetIds = [...testAssets.map((asset) => asset.assetId), invalidAssetId]; await expect(provider.getAssets({ assetIds })).rejects.toThrowError(ProviderError); diff --git a/packages/cardano-services/test/jest-setup/snapshots/asset.sql b/packages/cardano-services/test/jest-setup/snapshots/asset.sql index 26300e0c66f..f2641a38d7c 100644 --- a/packages/cardano-services/test/jest-setup/snapshots/asset.sql +++ b/packages/cardano-services/test/jest-setup/snapshots/asset.sql @@ -152,9 +152,9 @@ COPY public.asset (id, supply, first_mint_block_slot, nft_metadata_id) FROM stdi 542efbd30f970e5db97f9ae05d1bf8f5f5a61617350b72c22444340974425443 13500000000000000 1509 \N 542efbd30f970e5db97f9ae05d1bf8f5f5a61617350b72c22444340974455448 13500000000000000 1509 \N 542efbd30f970e5db97f9ae05d1bf8f5f5a61617350b72c224443409744d494e 13500000000000000 1509 \N -62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a5465737448616e646c65 1 1536 1 -62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a48656c6c6f48616e646c65 1 1536 2 -62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a446f75626c6548616e646c65 2 1536 3 +62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a7465737468616e646c65 1 1536 1 +62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68656c6c6f68616e646c65 1 1536 2 +62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a646f75626c6568616e646c65 2 1536 3 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a283232322968616e646c653638 1 1559 \N 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a000de14068616e646c6532 1 4123 16 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68616e646c6531 1 4123 17 @@ -1957,9 +1957,9 @@ COPY public.block_data (block_height, data) FROM stdin; -- COPY public.nft_metadata (id, name, description, image, media_type, files, type, other_properties, user_token_asset_id, parent_asset_id, created_at_slot) FROM stdin; -1 TestHandle The Handle Standard ipfs://some-hash image/jpeg [{"src": "ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2BU2dLjfWxuJoF2N5", "name": "some name", "mediaType": "video/mp4"}, {"src": "ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2BU2dLjfWxuJoF2Ny", "name": "some name", "mediaType": "audio/mpeg"}] CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a5465737448616e646c65 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a5465737448616e646c65 1536 -2 HelloHandle The Handle Standard ipfs://some-hash \N \N CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a48656c6c6f48616e646c65 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a48656c6c6f48616e646c65 1536 -3 DoubleHandle The Handle Standard ipfs://some-hash \N \N CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a446f75626c6548616e646c65 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a446f75626c6548616e646c65 1536 +1 testhandle The Handle Standard ipfs://some-hash image/jpeg [{"src": "ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2BU2dLjfWxuJoF2N5", "name": "some name", "mediaType": "video/mp4"}, {"src": "ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2BU2dLjfWxuJoF2Ny", "name": "some name", "mediaType": "audio/mpeg"}] CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a7465737468616e646c65 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a7465737468616e646c65 1536 +2 hellohandle The Handle Standard ipfs://some-hash \N \N CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68656c6c6f68616e646c65 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68656c6c6f68616e646c65 1536 +3 doublehandle The Handle Standard ipfs://some-hash \N \N CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a646f75626c6568616e646c65 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a646f75626c6568616e646c65 1536 4 $pharmers2 \N ipfs://zdj7WmoZ6Vy5d3K6uqBSRZPRzSebVxbL2n1WAQNAX3oLaWeYt image/jpeg \N CIP-0068 {"value": [["og", {"value": "0", "__type": "bigint"}], ["og_number", {"value": "0", "__type": "bigint"}], ["rarity", "basic"], ["length", {"value": "9", "__type": "bigint"}], ["characters", "letters,numbers"], ["numeric_modifiers", ""], ["version", {"value": "1", "__type": "bigint"}]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a000de14068616e646c6532 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a000643b068616e646c6532 4123 5 $handle1 The Handle Standard ipfs://some-hash \N \N CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68616e646c6531 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68616e646c6531 4123 6 $handle1 The Handle Standard ipfs://some-hash \N \N CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68616e646c6531 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68616e646c6531 4450 diff --git a/packages/cardano-services/test/jest-setup/snapshots/db_sync.sql b/packages/cardano-services/test/jest-setup/snapshots/db_sync.sql index d0175cf6649..57df0227031 100644 --- a/packages/cardano-services/test/jest-setup/snapshots/db_sync.sql +++ b/packages/cardano-services/test/jest-setup/snapshots/db_sync.sql @@ -4842,9 +4842,9 @@ COPY public.multi_asset (id, policy, name, fingerprint) FROM stdin; 2 \\x542efbd30f970e5db97f9ae05d1bf8f5f5a61617350b72c224443409 \\x74425443 asset1fztanl4w83ap4j3euh8e6f0s00739yctyypupz 3 \\x542efbd30f970e5db97f9ae05d1bf8f5f5a61617350b72c224443409 \\x74455448 asset1adpgdtn5j49ghal2nqvulvjer835xlv3x6w2zf 4 \\x542efbd30f970e5db97f9ae05d1bf8f5f5a61617350b72c224443409 \\x744d494e asset1esdmfgxz8k68957ye24uup4ee7q3pet6wrkg4a -5 \\x62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a \\x446f75626c6548616e646c65 asset1ss4nvcah07l2492qrfydamvukk4xdqme8k22vv -6 \\x62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a \\x48656c6c6f48616e646c65 asset13xe953tueyajgxrksqww9kj42erzvqygyr3phl -7 \\x62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a \\x5465737448616e646c65 asset1ne8rapyhga8jp95pemrefrgts9ht035zlmy6zj +5 \\x62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a \\x646f75626c6568616e646c65 asset1ss4nvcah07l2492qrfydamvukk4xdqme8k22vv +6 \\x62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a \\x68656c6c6f68616e646c65 asset13xe953tueyajgxrksqww9kj42erzvqygyr3phl +7 \\x62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a \\x7465737468616e646c65 asset1ne8rapyhga8jp95pemrefrgts9ht035zlmy6zj 8 \\x62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a \\x283232322968616e646c653638 asset1ju4qkyl4p9xszrgfxfmu909q90luzqu0nyh4u8 9 \\x62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a \\x000643b068616e646c6532 asset1vjzkdxns6ze7ph4880h3m3zghvesral9ryp2zq 10 \\x62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a \\x000de14068616e646c6532 asset1050jtqadfpvyfta8l86yrxgj693xws6l0qa87c @@ -10103,7 +10103,7 @@ COPY public.tx_in (id, tx_in_id, tx_out_id, tx_out_index, redeemer_id) FROM stdi -- COPY public.tx_metadata (id, key, json, bytes, tx_id) FROM stdin; -1 721 {"62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a": {"": {"core": {"og": 0, "prefix": "$", "version": 0, "termsofuse": "https://cardanofoundation.org/en/terms-and-conditions/", "handleEncoding": "utf-8"}, "name": "", "image": "ipfs://some-hash", "website": "https://cardano.org/", "description": "The Handle Standard", "augmentations": []}, "TestHandle": {"core": {"og": 0, "prefix": "$", "version": 0, "termsofuse": "https://cardanofoundation.org/en/terms-and-conditions/", "handleEncoding": "utf-8"}, "name": "TestHandle", "files": [{"src": "ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2BU2dLjfWxuJoF2N5", "name": "some name", "mediaType": "video/mp4"}, {"src": ["ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2", "BU2dLjfWxuJoF2Ny"], "name": "some name", "mediaType": "audio/mpeg"}], "image": "ipfs://some-hash", "website": "https://cardano.org/", "mediaType": "image/jpeg", "description": "The Handle Standard", "augmentations": []}, "HelloHandle": {"core": {"og": 0, "prefix": "$", "version": 0, "termsofuse": "https://cardanofoundation.org/en/terms-and-conditions/", "handleEncoding": "utf-8"}, "name": "HelloHandle", "image": "ipfs://some-hash", "website": "https://cardano.org/", "description": "The Handle Standard", "augmentations": []}, "DoubleHandle": {"core": {"og": 0, "prefix": "$", "version": 0, "termsofuse": "https://cardanofoundation.org/en/terms-and-conditions/", "handleEncoding": "utf-8"}, "name": "DoubleHandle", "image": "ipfs://some-hash", "website": "https://cardano.org/", "description": "The Handle Standard", "augmentations": []}}} \\xa11902d1a178383632313733623930623536376164346263663235346164306637366562333734643734396430623235666438323738366166366138333961a460a66d6175676d656e746174696f6e738064636f7265a56e68616e646c65456e636f64696e67657574662d38626f67006670726566697861246a7465726d736f66757365783668747470733a2f2f63617264616e6f666f756e646174696f6e2e6f72672f656e2f7465726d732d616e642d636f6e646974696f6e732f6776657273696f6e006b6465736372697074696f6e735468652048616e646c65205374616e6461726465696d61676570697066733a2f2f736f6d652d68617368646e616d656067776562736974657468747470733a2f2f63617264616e6f2e6f72672f6c446f75626c6548616e646c65a66d6175676d656e746174696f6e738064636f7265a56e68616e646c65456e636f64696e67657574662d38626f67006670726566697861246a7465726d736f66757365783668747470733a2f2f63617264616e6f666f756e646174696f6e2e6f72672f656e2f7465726d732d616e642d636f6e646974696f6e732f6776657273696f6e006b6465736372697074696f6e735468652048616e646c65205374616e6461726465696d61676570697066733a2f2f736f6d652d68617368646e616d656c446f75626c6548616e646c6567776562736974657468747470733a2f2f63617264616e6f2e6f72672f6b48656c6c6f48616e646c65a66d6175676d656e746174696f6e738064636f7265a56e68616e646c65456e636f64696e67657574662d38626f67006670726566697861246a7465726d736f66757365783668747470733a2f2f63617264616e6f666f756e646174696f6e2e6f72672f656e2f7465726d732d616e642d636f6e646974696f6e732f6776657273696f6e006b6465736372697074696f6e735468652048616e646c65205374616e6461726465696d61676570697066733a2f2f736f6d652d68617368646e616d656b48656c6c6f48616e646c6567776562736974657468747470733a2f2f63617264616e6f2e6f72672f6a5465737448616e646c65a86d6175676d656e746174696f6e738064636f7265a56e68616e646c65456e636f64696e67657574662d38626f67006670726566697861246a7465726d736f66757365783668747470733a2f2f63617264616e6f666f756e646174696f6e2e6f72672f656e2f7465726d732d616e642d636f6e646974696f6e732f6776657273696f6e006b6465736372697074696f6e735468652048616e646c65205374616e646172646566696c657382a3696d656469615479706569766964656f2f6d7034646e616d6569736f6d65206e616d65637372637835697066733a2f2f516d6237385151345258784b51727465526e34583357614d5858666d6932425532644c6a665778754a6f46324e35a3696d65646961547970656a617564696f2f6d706567646e616d6569736f6d65206e616d6563737263827825697066733a2f2f516d6237385151345258784b51727465526e34583357614d5858666d693270425532644c6a665778754a6f46324e7965696d61676570697066733a2f2f736f6d652d68617368696d65646961547970656a696d6167652f6a706567646e616d656a5465737448616e646c6567776562736974657468747470733a2f2f63617264616e6f2e6f72672f 109 +1 721 {"62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a": {"": {"core": {"og": 0, "prefix": "$", "version": 0, "termsofuse": "https://cardanofoundation.org/en/terms-and-conditions/", "handleEncoding": "utf-8"}, "name": "", "image": "ipfs://some-hash", "website": "https://cardano.org/", "description": "The Handle Standard", "augmentations": []}, "testhandle": {"core": {"og": 0, "prefix": "$", "version": 0, "termsofuse": "https://cardanofoundation.org/en/terms-and-conditions/", "handleEncoding": "utf-8"}, "name": "testhandle", "files": [{"src": "ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2BU2dLjfWxuJoF2N5", "name": "some name", "mediaType": "video/mp4"}, {"src": ["ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2", "BU2dLjfWxuJoF2Ny"], "name": "some name", "mediaType": "audio/mpeg"}], "image": "ipfs://some-hash", "website": "https://cardano.org/", "mediaType": "image/jpeg", "description": "The Handle Standard", "augmentations": []}, "hellohandle": {"core": {"og": 0, "prefix": "$", "version": 0, "termsofuse": "https://cardanofoundation.org/en/terms-and-conditions/", "handleEncoding": "utf-8"}, "name": "hellohandle", "image": "ipfs://some-hash", "website": "https://cardano.org/", "description": "The Handle Standard", "augmentations": []}, "doublehandle": {"core": {"og": 0, "prefix": "$", "version": 0, "termsofuse": "https://cardanofoundation.org/en/terms-and-conditions/", "handleEncoding": "utf-8"}, "name": "doublehandle", "image": "ipfs://some-hash", "website": "https://cardano.org/", "description": "The Handle Standard", "augmentations": []}}} \\xa11902d1a178383632313733623930623536376164346263663235346164306637366562333734643734396430623235666438323738366166366138333961a460a66d6175676d656e746174696f6e738064636f7265a56e68616e646c65456e636f64696e67657574662d38626f67006670726566697861246a7465726d736f66757365783668747470733a2f2f63617264616e6f666f756e646174696f6e2e6f72672f656e2f7465726d732d616e642d636f6e646974696f6e732f6776657273696f6e006b6465736372697074696f6e735468652048616e646c65205374616e6461726465696d61676570697066733a2f2f736f6d652d68617368646e616d656067776562736974657468747470733a2f2f63617264616e6f2e6f72672f6c646f75626c6568616e646c65a66d6175676d656e746174696f6e738064636f7265a56e68616e646c65456e636f64696e67657574662d38626f67006670726566697861246a7465726d736f66757365783668747470733a2f2f63617264616e6f666f756e646174696f6e2e6f72672f656e2f7465726d732d616e642d636f6e646974696f6e732f6776657273696f6e006b6465736372697074696f6e735468652048616e646c65205374616e6461726465696d61676570697066733a2f2f736f6d652d68617368646e616d656c646f75626c6568616e646c6567776562736974657468747470733a2f2f63617264616e6f2e6f72672f6b68656c6c6f68616e646c65a66d6175676d656e746174696f6e738064636f7265a56e68616e646c65456e636f64696e67657574662d38626f67006670726566697861246a7465726d736f66757365783668747470733a2f2f63617264616e6f666f756e646174696f6e2e6f72672f656e2f7465726d732d616e642d636f6e646974696f6e732f6776657273696f6e006b6465736372697074696f6e735468652048616e646c65205374616e6461726465696d61676570697066733a2f2f736f6d652d68617368646e616d656b68656c6c6f68616e646c6567776562736974657468747470733a2f2f63617264616e6f2e6f72672f6a7465737468616e646c65a86d6175676d656e746174696f6e738064636f7265a56e68616e646c65456e636f64696e67657574662d38626f67006670726566697861246a7465726d736f66757365783668747470733a2f2f63617264616e6f666f756e646174696f6e2e6f72672f656e2f7465726d732d616e642d636f6e646974696f6e732f6776657273696f6e006b6465736372697074696f6e735468652048616e646c65205374616e646172646566696c657382a3696d656469615479706569766964656f2f6d7034646e616d6569736f6d65206e616d65637372637835697066733a2f2f516d6237385151345258784b51727465526e34583357614d5858666d6932425532644c6a665778754a6f46324e35a3696d65646961547970656a617564696f2f6d706567646e616d6569736f6d65206e616d6563737263827825697066733a2f2f516d6237385151345258784b51727465526e34583357614d5858666d693270425532644c6a665778754a6f46324e7965696d61676570697066733a2f2f736f6d652d68617368696d65646961547970656a696d6167652f6a706567646e616d656a7465737468616e646c6567776562736974657468747470733a2f2f63617264616e6f2e6f72672f 109 2 721 {"62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a": {"handle1": {"core": {"og": 0, "prefix": "$", "version": 0, "termsofuse": "https://cardanofoundation.org/en/terms-and-conditions/", "handleEncoding": "utf-8"}, "name": "$handle1", "image": "ipfs://some-hash", "website": "https://cardano.org/", "description": "The Handle Standard", "augmentations": []}}} \\xa11902d1a178383632313733623930623536376164346263663235346164306637366562333734643734396430623235666438323738366166366138333961a16768616e646c6531a66d6175676d656e746174696f6e738064636f7265a56e68616e646c65456e636f64696e67657574662d38626f67006670726566697861246a7465726d736f66757365783668747470733a2f2f63617264616e6f666f756e646174696f6e2e6f72672f656e2f7465726d732d616e642d636f6e646974696f6e732f6776657273696f6e006b6465736372697074696f6e735468652048616e646c65205374616e6461726465696d61676570697066733a2f2f736f6d652d68617368646e616d65682468616e646c653167776562736974657468747470733a2f2f63617264616e6f2e6f72672f 111 3 721 {"62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a": {"handl": {"core": {"og": 0, "prefix": "$", "version": 0, "termsofuse": "https://cardanofoundation.org/en/terms-and-conditions/", "handleEncoding": "utf-8"}, "name": "$handl", "image": "ipfs://some-hash", "website": "https://cardano.org/", "description": "The Handle Standard", "augmentations": []}}} \\xa11902d1a178383632313733623930623536376164346263663235346164306637366562333734643734396430623235666438323738366166366138333961a16568616e646ca66d6175676d656e746174696f6e738064636f7265a56e68616e646c65456e636f64696e67657574662d38626f67006670726566697861246a7465726d736f66757365783668747470733a2f2f63617264616e6f666f756e646174696f6e2e6f72672f656e2f7465726d732d616e642d636f6e646974696f6e732f6776657273696f6e006b6465736372697074696f6e735468652048616e646c65205374616e6461726465696d61676570697066733a2f2f736f6d652d68617368646e616d65662468616e646c67776562736974657468747470733a2f2f63617264616e6f2e6f72672f 112 4 721 {"62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a": {"sub@handl": {"core": {"og": 0, "prefix": "$", "version": 0, "termsofuse": "https://cardanofoundation.org/en/terms-and-conditions/", "handleEncoding": "utf-8"}, "name": "$sub@handl", "image": "ipfs://some-hash", "website": "https://cardano.org/", "description": "The Handle Standard", "augmentations": []}}} \\xa11902d1a178383632313733623930623536376164346263663235346164306637366562333734643734396430623235666438323738366166366138333961a1697375624068616e646ca66d6175676d656e746174696f6e738064636f7265a56e68616e646c65456e636f64696e67657574662d38626f67006670726566697861246a7465726d736f66757365783668747470733a2f2f63617264616e6f666f756e646174696f6e2e6f72672f656e2f7465726d732d616e642d636f6e646974696f6e732f6776657273696f6e006b6465736372697074696f6e735468652048616e646c65205374616e6461726465696d61676570697066733a2f2f736f6d652d68617368646e616d656a247375624068616e646c67776562736974657468747470733a2f2f63617264616e6f2e6f72672f 113 diff --git a/packages/cardano-services/test/jest-setup/snapshots/handle.sql b/packages/cardano-services/test/jest-setup/snapshots/handle.sql index 396110d3908..91df79373df 100644 --- a/packages/cardano-services/test/jest-setup/snapshots/handle.sql +++ b/packages/cardano-services/test/jest-setup/snapshots/handle.sql @@ -364,9 +364,9 @@ addr_test1qqk9y8lt37jk5k4672nefy2ga9l3vxg3xdqgsvpgkq78httaz6v5pj3usegtaz2srkf3kv -- COPY public.asset (id, supply, first_mint_block_slot, nft_metadata_id) FROM stdin; -62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a5465737448616e646c65 1 1536 1 -62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a48656c6c6f48616e646c65 1 1536 2 -62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a446f75626c6548616e646c65 2 1536 3 +62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a7465737468616e646c65 1 1536 1 +62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68656c6c6f68616e646c65 1 1536 2 +62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a646f75626c6568616e646c65 2 1536 3 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a283232322968616e646c653638 1 1559 \N 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a000643b068616e646c6532 1 4123 \N 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a000de14068616e646c6532 1 4123 10 @@ -2160,8 +2160,8 @@ COPY public.block_data (block_height, data) FROM stdin; -- COPY public.handle (handle, cardano_address, policy_id, has_datum, default_for_stake_credential, default_for_payment_credential, asset_id, parent_handle_handle) FROM stdin; -HelloHandle addr_test1qr0c3frkem9cqn5f73dnvqpena27k2fgqew6wct9eaka03agfwkvzr0zyq7nqvcj24zehrshx63zzdxv24x3a4tcnfeq9zwmn7 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a f TestHandle TestHandle 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a48656c6c6f48616e646c65 \N -TestHandle addr_test1qr0c3frkem9cqn5f73dnvqpena27k2fgqew6wct9eaka03agfwkvzr0zyq7nqvcj24zehrshx63zzdxv24x3a4tcnfeq9zwmn7 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a f TestHandle TestHandle 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a5465737448616e646c65 \N +hellohandle addr_test1qr0c3frkem9cqn5f73dnvqpena27k2fgqew6wct9eaka03agfwkvzr0zyq7nqvcj24zehrshx63zzdxv24x3a4tcnfeq9zwmn7 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a f testhandle testhandle 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68656c6c6f68616e646c65 \N +testhandle addr_test1qr0c3frkem9cqn5f73dnvqpena27k2fgqew6wct9eaka03agfwkvzr0zyq7nqvcj24zehrshx63zzdxv24x3a4tcnfeq9zwmn7 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a f testhandle testhandle 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a7465737468616e646c65 \N handl addr_test1qpw0djgj0x59ngrjvqthn7enhvruxnsavsw5th63la3mjel3tkc974sr23jmlzgq5zda4gtv8k9cy38756r9y3qgmkqqjz6aa7 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a t handl handl 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a000de14068616e646c \N sub@handl addr_test1qpw0djgj0x59ngrjvqthn7enhvruxnsavsw5th63la3mjel3tkc974sr23jmlzgq5zda4gtv8k9cy38756r9y3qgmkqqjz6aa7 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a t handl handl 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a000de1407375624068616e646c handl handle2 addr_test1qpw0djgj0x59ngrjvqthn7enhvruxnsavsw5th63la3mjel3tkc974sr23jmlzgq5zda4gtv8k9cy38756r9y3qgmkqqjz6aa7 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a f handl handl 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a000de14068616e646c6532 \N @@ -2175,9 +2175,9 @@ virtual@handl addr_test1qpw0djgj0x59ngrjvqthn7enhvruxnsavsw5th63la3mjel3tkc974sr -- COPY public.handle_metadata (id, handle, og, profile_pic_image, background_image, output_id, block_slot) FROM stdin; -1 TestHandle f \N \N \N 1536 -2 HelloHandle f \N \N \N 1536 -3 DoubleHandle f \N \N \N 1536 +1 testhandle f \N \N \N 1536 +2 hellohandle f \N \N \N 1536 +3 doublehandle f \N \N \N 1536 4 handle2 f ipfs://QmWgjXCxVUSWPy1WmUV3joPP1sZMvZ71so6qy6C2ZukRBD ipfs://QmY6XiqBr9JNnuguTRsx3ocvkQemNJ5iCRMie85wqz94Jo 3 4123 5 handle1 f \N \N \N 4123 6 handle1 f \N \N \N 4450 @@ -2194,9 +2194,9 @@ COPY public.handle_metadata (id, handle, og, profile_pic_image, background_image -- COPY public.nft_metadata (id, name, description, image, media_type, files, type, other_properties, user_token_asset_id, parent_asset_id, created_at_slot) FROM stdin; -1 TestHandle The Handle Standard ipfs://some-hash image/jpeg [{"src": "ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2BU2dLjfWxuJoF2N5", "name": "some name", "mediaType": "video/mp4"}, {"src": "ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2BU2dLjfWxuJoF2Ny", "name": "some name", "mediaType": "audio/mpeg"}] CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a5465737448616e646c65 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a5465737448616e646c65 1536 -2 HelloHandle The Handle Standard ipfs://some-hash \N \N CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a48656c6c6f48616e646c65 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a48656c6c6f48616e646c65 1536 -3 DoubleHandle The Handle Standard ipfs://some-hash \N \N CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a446f75626c6548616e646c65 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a446f75626c6548616e646c65 1536 +1 testhandle The Handle Standard ipfs://some-hash image/jpeg [{"src": "ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2BU2dLjfWxuJoF2N5", "name": "some name", "mediaType": "video/mp4"}, {"src": "ipfs://Qmb78QQ4RXxKQrteRn4X3WaMXXfmi2BU2dLjfWxuJoF2Ny", "name": "some name", "mediaType": "audio/mpeg"}] CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a7465737468616e646c65 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a7465737468616e646c65 1536 +2 hellohandle The Handle Standard ipfs://some-hash \N \N CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68656c6c6f68616e646c65 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68656c6c6f68616e646c65 1536 +3 doublehandle The Handle Standard ipfs://some-hash \N \N CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a646f75626c6568616e646c65 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a646f75626c6568616e646c65 1536 4 $pharmers2 \N ipfs://zdj7WmoZ6Vy5d3K6uqBSRZPRzSebVxbL2n1WAQNAX3oLaWeYt image/jpeg \N CIP-0068 {"value": [["og", {"value": "0", "__type": "bigint"}], ["og_number", {"value": "0", "__type": "bigint"}], ["rarity", "basic"], ["length", {"value": "9", "__type": "bigint"}], ["characters", "letters,numbers"], ["numeric_modifiers", ""], ["version", {"value": "1", "__type": "bigint"}]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a000de14068616e646c6532 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a000643b068616e646c6532 4123 5 $handle1 The Handle Standard ipfs://some-hash \N \N CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68616e646c6531 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68616e646c6531 4123 6 $handle1 The Handle Standard ipfs://some-hash \N \N CIP-0025 {"value": [["augmentations", []], ["core", {"value": [["handleEncoding", "utf-8"], ["og", {"value": "0", "__type": "bigint"}], ["prefix", "$"], ["termsofuse", "https://cardanofoundation.org/en/terms-and-conditions/"], ["version", {"value": "0", "__type": "bigint"}]], "__type": "Map"}], ["website", "https://cardano.org/"]], "__type": "Map"} 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68616e646c6531 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68616e646c6531 4450 @@ -2272,9 +2272,9 @@ COPY public.stake_key_registration (id, stake_key_hash, block_slot) FROM stdin; -- COPY public.tokens (id, quantity, asset_id, output_id) FROM stdin; -1 2 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a446f75626c6548616e646c65 1 -2 1 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a48656c6c6f48616e646c65 1 -3 1 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a5465737448616e646c65 1 +1 2 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a646f75626c6568616e646c65 1 +2 1 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a68656c6c6f68616e646c65 1 +3 1 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a7465737468616e646c65 1 4 1 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a283232322968616e646c653638 2 5 1 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a000643b068616e646c6532 3 6 1 62173b90b567ad4bcf254ad0f76eb374d749d0b25fd82786af6a839a000de14068616e646c6532 3 diff --git a/packages/core/src/Asset/util/isValidHandle.ts b/packages/core/src/Asset/util/isValidHandle.ts index 2eb3b452d2e..e7a1b9a21b1 100644 --- a/packages/core/src/Asset/util/isValidHandle.ts +++ b/packages/core/src/Asset/util/isValidHandle.ts @@ -1,4 +1,26 @@ +/* eslint-disable unicorn/better-regex */ +// From ADA Handle Discord: slightly modified to not allow uppercase characters +const REGEX_HANDLE = new RegExp(/^[a-z0-9_.-]{1,15}$/); +// From ADA Handle Discord +const REGEX_SUB_HANDLE = new RegExp(/(?:^[a-z0-9_.-]{1,15}$)|(?:^(?!.{29})[a-z0-9_.-]+@[a-z0-9_.-]{1,15}$)/g); + +/** + * From ADA Handle FAQ: + * Alphanumeric: [a-z][0-9] + * Dash: - + * Underscore: _ + * Period: . + * + * Since handles are case-insensitive, this function only allows lowercase. + * Max 1-15 characters (or 3-31 characters for a subhandle) + */ export const isValidHandle = (handle: string) => { - const pattern = /^[\w,.\-]*@{0,1}[\w,.\-]+$/; - return pattern.test(handle); + // 'g' modifier makes it stateful + REGEX_SUB_HANDLE.lastIndex = 0; + return ( + REGEX_HANDLE.test(handle) || + REGEX_SUB_HANDLE.test(handle) || + // Pipe | in general is not valid, but an exception exists in mainnet + handle === '0|0' + ); }; diff --git a/packages/core/test/Asset/util/isValidHandle.test.ts b/packages/core/test/Asset/util/isValidHandle.test.ts index 04578a2d8cf..2d1ae7244f0 100644 --- a/packages/core/test/Asset/util/isValidHandle.test.ts +++ b/packages/core/test/Asset/util/isValidHandle.test.ts @@ -4,26 +4,39 @@ const validHandles = [ 'bob', 'a', 'alice', + '_', 'test-handle', 'test.handle', - 'test-handle-123', - 'test-handle-123.456', + 'test-handle.123', 'handle_name', 'handle_name', - '@alice', - 'test-@handle' + 'a@alice', + 'alice@a', + 'test-@handle', + // Exists on mainnet + '0|0' ]; const invalidHandles = [ + 'Alice', 'bob!', '$wallet', 'alice@', + '@alice', 'test*handle#2', 'lace&bob', 'ada%1_test', 'lace ', - 'wallet ', - 'sub@sub@handle' + 'wallet|', + 'comma,', + 'comma,@sub', + 'sub@comma,', + '\n', + '\r', + ' ', + 'sub@sub@handle', + // too long + 'test-handle-123.456' ]; describe('isValidHandle', () => { diff --git a/packages/e2e/local-network/scripts/mint-handles.sh b/packages/e2e/local-network/scripts/mint-handles.sh index b4f67edd1b0..81b0b4bb3ff 100755 --- a/packages/e2e/local-network/scripts/mint-handles.sh +++ b/packages/e2e/local-network/scripts/mint-handles.sh @@ -2,8 +2,8 @@ source $(dirname $0)/common.sh -handleNames=("HelloHandle" "TestHandle" "DoubleHandle") -handleHexes=("48656c6c6f48616e646c65" "5465737448616e646c65" "446f75626c6548616e646c65") +handleNames=("hellohandle" "testhandle" "doublehandle") +handleHexes=("68656c6c6f68616e646c65" "7465737468616e646c65" "646f75626c6568616e646c65") # addr, payment.skey and payment.vkey are generated from mnemonic # vacant violin soft weird deliver render brief always monitor general maid smart jelly core drastic erode echo there clump dizzy card filter option defense diff --git a/packages/e2e/test/providers/HandleProvider.test.ts b/packages/e2e/test/providers/HandleProvider.test.ts index fe6027e5454..1d1a0306d9d 100644 --- a/packages/e2e/test/providers/HandleProvider.test.ts +++ b/packages/e2e/test/providers/HandleProvider.test.ts @@ -11,13 +11,13 @@ describe('HandleProvider', () => { it('resolves handle', async () => { const policyPath = path.join(__dirname, '../../local-network/sdk-ipc/handle_policy_ids'); const policyId = fs.readFileSync(policyPath, 'utf8').toString().trim(); - const handleName = 'HelloHandle'; // handle minted in mint-handles.sh - const handleName2 = 'TestHandle'; + const handleName = 'hellohandle'; // handle minted in mint-handles.sh + const handleName2 = 'testhandle'; const config = { baseUrl: env.HANDLE_PROVIDER_PARAMS.baseUrl, logger }; const handleProvider = handleHttpProvider(config); const handle = await handleProvider.resolveHandles({ handles: [handleName, handleName2] }); expect(handle.length).toEqual(2); - expect(handle[0]?.handle).toEqual('HelloHandle'); + expect(handle[0]?.handle).toEqual('hellohandle'); expect(handle[0]?.hasDatum).toEqual(false); expect(handle[0]?.policyId).toEqual(policyId); expect(handle[0]?.resolvedAt).toBeDefined(); From 726f945e25dd2eef67c37fbafff6e1863dcb11f2 Mon Sep 17 00:00:00 2001 From: Martynas Kazlauskas Date: Thu, 8 Feb 2024 19:38:36 +0200 Subject: [PATCH 2/2] fix(projection): do not throw when encountering a handle with invalid name --- .../projection/src/operators/Mappers/util.ts | 9 +- .../operators/Mappers/withHandleMetadata.ts | 4 +- .../src/operators/Mappers/withHandles.ts | 9 +- .../test/operators/Mappers/handleUtil.ts | 1 + .../Mappers/withHandleMetadata.test.ts | 85 +++++++++++-------- .../operators/Mappers/withHandles.test.ts | 78 ++++++++++++++--- 6 files changed, 132 insertions(+), 54 deletions(-) diff --git a/packages/projection/src/operators/Mappers/util.ts b/packages/projection/src/operators/Mappers/util.ts index 90e4aa1fe3d..4aeaeac24d4 100644 --- a/packages/projection/src/operators/Mappers/util.ts +++ b/packages/projection/src/operators/Mappers/util.ts @@ -1,11 +1,14 @@ import { Asset, Cardano, Handle } from '@cardano-sdk/core'; -import { InvalidStringError } from '@cardano-sdk/util'; +import { Logger } from 'ts-log'; /** Up to 100k transactions per block. Fits in 64-bit signed integer. */ export const computeCompactTxId = (blockHeight: number, txIndex: number) => blockHeight * 100_000 + txIndex; -export const assetNameToUTF8Handle = (assetName: Cardano.AssetName): Handle => { +export const assetNameToUTF8Handle = (assetName: Cardano.AssetName, logger: Logger): Handle | null => { const handle = Cardano.AssetName.toUTF8(assetName); - if (!Asset.util.isValidHandle(handle)) throw new InvalidStringError(`Invalid handle ${handle}`); + if (!Asset.util.isValidHandle(handle)) { + logger.warn(`Invalid handle: '${handle}' / '${assetName}'`); + return null; + } return handle; }; diff --git a/packages/projection/src/operators/Mappers/withHandleMetadata.ts b/packages/projection/src/operators/Mappers/withHandleMetadata.ts index 00679f14871..8e6bae8d4fa 100644 --- a/packages/projection/src/operators/Mappers/withHandleMetadata.ts +++ b/packages/projection/src/operators/Mappers/withHandleMetadata.ts @@ -73,8 +73,8 @@ const getHandleMetadata = ( .map(({ nftMetadata, userTokenAssetId, referenceTokenAssetId, extra }): HandleMetadata | undefined => { const cip67Asset = referenceTokenAssetId && cip67Assets.byAssetId[referenceTokenAssetId]; const handle = cip67Asset - ? assetNameToUTF8Handle(cip67Asset!.decoded.content) - : assetNameToUTF8Handle(Cardano.AssetId.getAssetName(userTokenAssetId)); + ? assetNameToUTF8Handle(cip67Asset!.decoded.content, logger) + : assetNameToUTF8Handle(Cardano.AssetId.getAssetName(userTokenAssetId), logger); if (!handle) return; return { handle, diff --git a/packages/projection/src/operators/Mappers/withHandles.ts b/packages/projection/src/operators/Mappers/withHandles.ts index a26b17714ed..032cd6ea9ae 100644 --- a/packages/projection/src/operators/Mappers/withHandles.ts +++ b/packages/projection/src/operators/Mappers/withHandles.ts @@ -27,18 +27,18 @@ export interface WithHandles { handles: HandleOwnership[]; } -const assetIdToUTF8Handle = (assetId: Cardano.AssetId, cip67Asset: CIP67Asset | undefined) => { +const assetIdToUTF8Handle = (assetId: Cardano.AssetId, cip67Asset: CIP67Asset | undefined, logger: Logger) => { if (cip67Asset) { if ( cip67Asset.decoded.label === Asset.AssetNameLabelNum.UserNFT || cip67Asset.decoded.label === Asset.AssetNameLabelNum.VirtualHandle ) { - return Cardano.AssetName.toUTF8(cip67Asset.decoded.content); + return assetNameToUTF8Handle(cip67Asset.decoded.content, logger); } // Ignore all but UserNFT cip67 assets return null; } - return assetNameToUTF8Handle(Cardano.AssetId.getAssetName(assetId)); + return assetNameToUTF8Handle(Cardano.AssetId.getAssetName(assetId), logger); }; const getHandleMetadata = (handleDataFields: Cardano.PlutusList, logger: Logger) => { @@ -83,7 +83,8 @@ const tryCreateHandleOwnership = ( try { const cip67Asset = cip67Assets.byAssetId[assetId]; - const handle = assetIdToUTF8Handle(assetId, cip67Asset); + const handle = assetIdToUTF8Handle(assetId, cip67Asset, logger); + if (!handle) return; const subhandleProps: Partial = {}; if (handle) { if (handle.includes('@')) { diff --git a/packages/projection/test/operators/Mappers/handleUtil.ts b/packages/projection/test/operators/Mappers/handleUtil.ts index 45b3879e959..f054e1e6b32 100644 --- a/packages/projection/test/operators/Mappers/handleUtil.ts +++ b/packages/projection/test/operators/Mappers/handleUtil.ts @@ -17,6 +17,7 @@ export const maryAddress = Cardano.PaymentAddress( export const bobHandleOne = 'bob.handle.one'; export const bobHandleTwo = 'bob.handle.two'; export const maryHandleOne = 'mary.handle.one'; +export const invalidHandle = '@#!'; export const virtualHandle = 'virtual@handl'; export const NFTHandle = 'sub@handl'; export const handleOutputs = { diff --git a/packages/projection/test/operators/Mappers/withHandleMetadata.test.ts b/packages/projection/test/operators/Mappers/withHandleMetadata.test.ts index 250e53b620a..253a556f951 100644 --- a/packages/projection/test/operators/Mappers/withHandleMetadata.test.ts +++ b/packages/projection/test/operators/Mappers/withHandleMetadata.test.ts @@ -1,10 +1,11 @@ -import { Cardano } from '@cardano-sdk/core'; +import { Cardano, Handle } from '@cardano-sdk/core'; import { ProjectionEvent } from '../../../src'; import { assetIdFromHandle, handleAssetName, handleOutputs, handlePolicyId, + invalidHandle, maryHandleOne, referenceNftOutput, userNftOutput @@ -29,43 +30,45 @@ const project = (tx: Cardano.OnChainTx) => ) ); +const createCip25HandleMetadata = (handle: Handle) => ({ + blob: new Map([ + [ + 721n, + new Map([ + [ + handlePolicyId, + new Map([ + [ + handleAssetName(handle), + new Map([ + ['name', `$${handle}`], + ['description', 'The Handle Standard'], + ['website', 'https://adahandle.com'], + ['image', 'ipfs://QmZqUk6nGqYJZzHiCGzbzqppA5qE99yNkuTSHuRQpymE1X'], + [ + 'core', + new Map([ + ['og', 1n], + ['termsofuse', 'https://adahandle.com/tou'], + ['handleEncoding', 'utf-8'], + ['prefix', '$'], + ['version', 0n] + ]) + ], + ['augmentations', []] + ]) + ] + ]) + ] + ]) + ] + ]) +}); + describe('withHandleMetadata', () => { it('maps "og" when handle is minted with cip25 metadata', async () => { const { handleMetadata } = await project({ - auxiliaryData: { - blob: new Map([ - [ - 721n, - new Map([ - [ - handlePolicyId, - new Map([ - [ - handleAssetName(maryHandleOne), - new Map([ - ['name', '$mary'], - ['description', 'The Handle Standard'], - ['website', 'https://adahandle.com'], - ['image', 'ipfs://QmZqUk6nGqYJZzHiCGzbzqppA5qE99yNkuTSHuRQpymE1X'], - [ - 'core', - new Map([ - ['og', 1n], - ['termsofuse', 'https://adahandle.com/tou'], - ['handleEncoding', 'utf-8'], - ['prefix', '$'], - ['version', 0n] - ]) - ], - ['augmentations', []] - ]) - ] - ]) - ] - ]) - ] - ]) - }, + auxiliaryData: createCip25HandleMetadata(maryHandleOne), body: { mint: new Map([[assetIdFromHandle(maryHandleOne), 1n]]), outputs: [handleOutputs.oneHandleMary] @@ -77,6 +80,18 @@ describe('withHandleMetadata', () => { expect(handleMetadata[0].txOut).toBeUndefined(); }); + it('ignores handles with invalid name', async () => { + const { handleMetadata } = await project({ + auxiliaryData: createCip25HandleMetadata(invalidHandle), + body: { + mint: new Map([[assetIdFromHandle(invalidHandle), 1n]]), + outputs: [handleOutputs.oneHandleMary] + } + } as Cardano.OnChainTx); + + expect(handleMetadata).toHaveLength(0); + }); + describe('cip68', () => { it('maps metadata fields when only reference token is present', async () => { const { handleMetadata } = await project({ diff --git a/packages/projection/test/operators/Mappers/withHandles.test.ts b/packages/projection/test/operators/Mappers/withHandles.test.ts index 677308815d1..f12d3b2c9a9 100644 --- a/packages/projection/test/operators/Mappers/withHandles.test.ts +++ b/packages/projection/test/operators/Mappers/withHandles.test.ts @@ -1,5 +1,6 @@ import { Asset, Cardano } from '@cardano-sdk/core'; import { Buffer } from 'buffer'; +import { CIP67Assets, withCIP67, withHandles, withMint, withUtxo } from '../../../src/operators/Mappers'; import { Mappers, ProjectionEvent } from '../../../src'; import { NFTSubHandleOutput, @@ -7,8 +8,10 @@ import { bobAddress, bobHandleOne, bobHandleTwo, + handleDatum, handleOutputs, handlePolicyId, + invalidHandle, maryAddress, maryHandleOne, referenceNftOutput, @@ -19,7 +22,6 @@ import { } from './handleUtil'; import { firstValueFrom, of } from 'rxjs'; import { logger, mockProviders } from '@cardano-sdk/util-dev'; -import { withCIP67, withHandles, withMint, withUtxo } from '../../../src/operators/Mappers'; type In = Mappers.WithMint & Mappers.WithCIP67 & Mappers.WithNftMetadata; @@ -174,8 +176,23 @@ describe('withHandles', () => { }); describe('assets with invalid asset names', () => { - const invalidAssetName = Asset.AssetNameLabel.encode(Cardano.AssetName('abc'), Asset.AssetNameLabelNum.UserFT); + const invalidAssetName = Cardano.AssetName(Buffer.from(invalidHandle, 'utf8').toString('hex')); const invalidAssetId = Cardano.AssetId.fromParts(handlePolicyId, invalidAssetName); + const decodedInvalidAssetName = Cardano.AssetName(Buffer.from(`${invalidHandle}other`, 'utf8').toString('hex')); + const invalidAssetCip67AssetName = Asset.AssetNameLabel.encode( + decodedInvalidAssetName, + Asset.AssetNameLabelNum.UserNFT + ); + const invalidAssetCip67ReferenceNftAssetName = Asset.AssetNameLabel.encode( + decodedInvalidAssetName, + Asset.AssetNameLabelNum.ReferenceNFT + ); + const invalidCip67AssetId = Cardano.AssetId.fromParts(handlePolicyId, invalidAssetCip67AssetName); + const invalidCip67ReferenceNftAssetId = Cardano.AssetId.fromParts( + handlePolicyId, + invalidAssetCip67ReferenceNftAssetName + ); + const outputsWithInvalidHandles = { invalidAssetName: { address: 'addr_test1vptwv4jvaqt635jvthpa29lww3vkzypm8l6vk4lv4tqfhhgajdgwf', @@ -184,17 +201,58 @@ describe('withHandles', () => { coins: 1n } }, - oneValidAndOneInvalidAssetName: { - address: 'addr_test1vptwv4jvaqt635jvthpa29lww3vkzypm8l6vk4lv4tqfhhgajdgwf', + oneValidTwoInvalidAssetName: { + address: Cardano.PaymentAddress('addr_test1vptwv4jvaqt635jvthpa29lww3vkzypm8l6vk4lv4tqfhhgajdgwf'), + datum: handleDatum, value: { assets: new Map([ [invalidAssetId, 1n], - [assetIdFromHandle(bobHandleTwo), 1n] - ]) + [assetIdFromHandle(bobHandleTwo), 1n], + [invalidCip67AssetId, 1n], + [invalidCip67ReferenceNftAssetId, 1n] + ]), + coins: 123n } } }; + const txId = Cardano.TransactionId('0000000000000000000000000000000000000000000000000000000000000000'); + const utxo: [Cardano.TxIn, Cardano.TxOut] = [ + { index: 0, txId }, + outputsWithInvalidHandles.oneValidTwoInvalidAssetName + ]; + const userNftCip67Asset = { + assetId: invalidCip67ReferenceNftAssetId, + assetName: invalidAssetCip67ReferenceNftAssetName, + decoded: { + content: decodedInvalidAssetName, + label: Asset.AssetNameLabelNum.ReferenceNFT + }, + policyId: handlePolicyId, + utxo + }; + const referenceNftCip67Asset = { + assetId: invalidCip67AssetId, + assetName: invalidAssetCip67AssetName, + decoded: { + content: decodedInvalidAssetName, + label: Asset.AssetNameLabelNum.UserNFT + }, + policyId: handlePolicyId, + utxo + }; + + const cip67: CIP67Assets = { + byAssetId: { + [invalidAssetCip67ReferenceNftAssetName]: userNftCip67Asset, + [invalidCip67AssetId]: referenceNftCip67Asset + }, + byLabel: { + [Asset.AssetNameLabelNum.UserNFT]: [userNftCip67Asset], + [Asset.AssetNameLabelNum.ReferenceNFT]: [referenceNftCip67Asset] + } + }; + it('it returns no handles when output only contain invalid assetId', async () => { const validTxSource$ = of({ block: { @@ -222,12 +280,12 @@ describe('withHandles', () => { body: [ { body: { - outputs: [outputsWithInvalidHandles.oneValidAndOneInvalidAssetName] + outputs: [outputsWithInvalidHandles.oneValidTwoInvalidAssetName] } } ] }, - cip67: { byAssetId: {}, byLabel: {} } + cip67 } as ProjectionEvent); const { handles } = await firstValueFrom( @@ -243,7 +301,7 @@ describe('withHandles', () => { body: [ { body: { - outputs: [outputsWithInvalidHandles.oneValidAndOneInvalidAssetName] + outputs: [outputsWithInvalidHandles.oneValidTwoInvalidAssetName] } }, { @@ -253,7 +311,7 @@ describe('withHandles', () => { } ] }, - cip67: { byAssetId: {}, byLabel: {} } + cip67 } as ProjectionEvent); const { handles } = await firstValueFrom(