Skip to content

Commit

Permalink
feat: update Handle entity and HandleStore to save parent handles
Browse files Browse the repository at this point in the history
 - update migration with handle parent migration for parent handles
 - store parent handle data in db
 - fix mint-handles.js script that was broken due to refactor
 - extend withHandles mappers to extract subhandle data
 - fix e2e test to take more handle data
 - generate sql data for subhandles to be used in tests
 - extend TypeOrmHandleProvider to resolve parent handles
  • Loading branch information
VanessaPC committed Dec 13, 2023
1 parent 8e1b834 commit 3fa3920
Show file tree
Hide file tree
Showing 19 changed files with 15,024 additions and 8,663 deletions.
2 changes: 2 additions & 0 deletions packages/cardano-services/src/Handle/TypeOrmHandleProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class TypeOrmHandleProvider extends TypeormProvider implements HandleProv
cardanoAddress,
handle,
hasDatum,
parentHandle,
policyId,
defaultForPaymentCredential,
defaultForStakeCredential,
Expand Down Expand Up @@ -77,6 +78,7 @@ export class TypeOrmHandleProvider extends TypeormProvider implements HandleProv
handle: handle!,
hasDatum: !!hasDatum,
image: nftMetadataEntity?.image,
parentHandle: parentHandle?.handle,
policyId: policyId!,
profilePic: handleMetadataEntity?.profilePicImage || undefined,
resolvedAt
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { HandleEntity } from '@cardano-sdk/projection-typeorm';
import { MigrationInterface, QueryRunner } from 'typeorm';

export class HandleParentMigration1700556589063 implements MigrationInterface {
static entity = HandleEntity;

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE "handle" ADD COLUMN "parent_handle_handle" character varying');
await queryRunner.query(
'ALTER TABLE "handle" ADD CONSTRAINT "FK_handle_parent_handle_handle" FOREIGN KEY ("parent_handle_handle") REFERENCES "handle"("handle") ON DELETE CASCADE ON UPDATE NO ACTION'
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE "handle" DROP CONSTRAINT "FK_handle_parent_handle_handle"');
await queryRunner.query('ALTER TABLE "handle" DROP COLUMN "parent_handle_handle"');
}
}
4 changes: 3 additions & 1 deletion packages/cardano-services/src/Projection/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { FkPoolRegistrationMigration1682519108369 } from './1682519108369-fk-poo
import { FkPoolRetirementMigration1682519108370 } from './1682519108370-fk-pool-retirement';
import { HandleDefaultMigrations1693830294136 } from './1693830294136-handle-default-columns';
import { HandleMetadataTableMigrations1693490983715 } from './1693490983715-handle-metadata-table';
import { HandleParentMigration1700556589063 } from './1700556589063-handle-parent';
import { HandleTableMigration1686138943349 } from './1686138943349-handle-table';
import { NftMetadataTableMigration1690269355640 } from './1690269355640-nft-metadata-table';
import { OutputTableMigration1682519108367 } from './1682519108367-output-table';
Expand Down Expand Up @@ -49,5 +50,6 @@ export const migrations: ProjectionMigration[] = [
HandleDefaultMigrations1693830294136,
PoolDelistedTableMigration1695899010515,
CurrentStakePollMetricsAttributesMigrations1698174358997,
PoolRewardsTableMigrations1698175956871
PoolRewardsTableMigrations1698175956871,
HandleParentMigration1700556589063
];
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ describe('TypeOrmHandleProvider', () => {
});
});

describe('resolve sub-handles', () => {
it('fetches parent handle of virtual subhandle', async () => {
const resolution = await provider.resolveHandles({ handles: ['virtual@handl'] });
expect(resolution[0]?.parentHandle).toBe('handl');
expect(resolution[0]?.cardanoAddress?.startsWith('addr')).toBe(true);
});
it('fetches parent handle of NFT subhandle', async () => {
const resolution = await provider.resolveHandles({ handles: ['sub@handl'] });
expect(resolution[0]?.parentHandle).toBe('handl');
expect(resolution[0]?.cardanoAddress?.startsWith('addr')).toBe(true);
});
});

// Test data is sourced from the test database snapshot
// packages/cardano-services/test/jest-setup/snapshots/handle.sql
it('fetches all distinct policy ids', async () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/cardano-services/test/jest-setup/mint-handles.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const {
const { firstValueFrom } = require('rxjs');
const { logger } = require('@cardano-sdk/util-dev');
const path = require('path');
const { SodiumBip32Ed25519 } = require('@cardano-sdk/crypto');

(async () => {
const wallet = (await getWallet({ env, idx: 0, logger, name: 'Handle Init Wallet', polling: { interval: 50 } }))
Expand All @@ -21,7 +22,7 @@ const path = require('path');
const keyAgent = await createStandaloneKeyAgent(
env.KEY_MANAGEMENT_PARAMS.mnemonic.split(' '),
await firstValueFrom(wallet.genesisParameters$),
await wallet.keyAgent.getBip32Ed25519()
new SodiumBip32Ed25519()
);

const policyId = await getHandlePolicyId(path.join(pathToE2ePackage, 'local-network', 'sdk-ipc'));
Expand Down
3,239 changes: 1,800 additions & 1,439 deletions packages/cardano-services/test/jest-setup/snapshots/asset.sql

Large diffs are not rendered by default.

12,802 changes: 8,667 additions & 4,135 deletions packages/cardano-services/test/jest-setup/snapshots/db_sync.sql

Large diffs are not rendered by default.

3,361 changes: 1,880 additions & 1,481 deletions packages/cardano-services/test/jest-setup/snapshots/handle.sql

Large diffs are not rendered by default.

3,620 changes: 2,049 additions & 1,571 deletions packages/cardano-services/test/jest-setup/snapshots/stake_pool.sql

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/core/src/Provider/HandleProvider/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface HandleResolution {
backgroundImage?: Asset.Uri;
profilePic?: Asset.Uri;
resolvedAt?: Point;
parentHandle?: Handle;
}

export interface ResolveHandlesArgs {
Expand Down
87 changes: 74 additions & 13 deletions packages/e2e/src/util/handle-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,27 @@ const handleDatum = Serialization.PlutusData.fromCbor(
)
).toCore();

const handlDatum = Serialization.PlutusData.fromCbor(
HexBlob(
// https://preview.cexplorer.io/datum/ff1a404ece117cc4482d26b072e30b5a6b3cd055a22debda3f90d704957e273a
'd8799faa446e616d654524686e646c45696d6167655838697066733a2f2f7a623272685a6a4c4a545838615a6d4a7a42424862366b7535446d6e6650674d47375a6d73627162317366736356365970496d65646961547970654a696d6167652f6a706567426f6700496f675f6e756d626572004672617269747946636f6d6d6f6e466c656e677468044a63686172616374657273476c657474657273516e756d657269635f6d6f64696669657273404776657273696f6e0101af4e7374616e646172645f696d6167655838697066733a2f2f7a623272685a6a4c4a545838615a6d4a7a42424862366b7535446d6e6650674d47375a6d7362716231736673635636597046706f7274616c404864657369676e65724047736f6369616c73404676656e646f72404764656661756c7400536c6173745f7570646174655f61646472657373583900f541f0822d4794e6d1ddc3c0d5e932585bfcce2d869b1c2ee05b1dc7c37bace64b57b50a044bbafa593811a6f49c9d8d8c0b187932e2df404c76616c6964617465645f6279581c4da965a049dfd15ed1ee19fba6e2974a0b79fc416dd1796a1f97f5e14a696d6167655f68617368584032646465376163633062376532333931626633326133646537643566313763356365663231633336626432333564636663643738376463663439656661363339537374616e646172645f696d6167655f686173685840326464653761636330623765323339316266333261336465376435663137633563656632316333366264323335646366636437383764636634396566613633394b7376675f76657273696f6e45322e302e314c6167726565645f7465726d7340546d6967726174655f7369675f72657175697265640045747269616c00446e73667700ff'
)
).toCore();

const subhandlDatum = Serialization.PlutusData.fromCbor(
HexBlob(
// https://preview.cexplorer.io/datum/29294f077464c36e67b304ad22547fb3dfa946623b0b2cbae8acea7fb299353c
'd8799faa446e616d65492473756240686e646c45696d6167655838697066733a2f2f7a6232726862426e7a6e4e48716748624a58786d71596a47714663377947314a444e6741664d3534726472455032776366496d65646961547970654a696d6167652f6a706567426f6700496f675f6e756d6265720046726172697479456261736963466c656e677468084a63686172616374657273476c657474657273516e756d657269635f6d6f64696669657273404776657273696f6e0101af4e7374616e646172645f696d6167655838697066733a2f2f7a6232726862426e7a6e4e48716748624a58786d71596a47714663377947314a444e6741664d353472647245503277636646706f7274616c404864657369676e65724047736f6369616c73404676656e646f72404764656661756c7400536c6173745f7570646174655f61646472657373583900f541f0822d4794e6d1ddc3c0d5e932585bfcce2d869b1c2ee05b1dc7c37bace64b57b50a044bbafa593811a6f49c9d8d8c0b187932e2df404c76616c6964617465645f6279581c4da965a049dfd15ed1ee19fba6e2974a0b79fc416dd1796a1f97f5e14a696d6167655f68617368584034333831373362613630333931353466646232643137383763363765633636333863393462643331633835336630643964356166343365626462313864623934537374616e646172645f696d6167655f686173685840343338313733626136303339313534666462326431373837633637656336363338633934626433316338353366306439643561663433656264623138646239344b7376675f76657273696f6e45322e302e314c6167726565645f7465726d7340546d6967726174655f7369675f72657175697265640045747269616c00446e73667700ff'
)
).toCore();

const virtualhandlDatum = Serialization.PlutusData.fromCbor(
HexBlob(
// https://preview.cexplorer.io/datum/e87d179ddf8ca2365fdb342101cc0f94f525d5e2ae2cb94085f28b84641c97e8
'd8799faf446e616d654d247669727475616c40686e646c45696d6167655838697066733a2f2f7a623272686b52636a5471546e5a387462704635485a474e4c4e355473324554633558477039576264614b415134335472496d65646961547970654a696d6167652f6a706567426f6700496f675f6e756d6265720046726172697479456261736963466c656e6774680c4a63686172616374657273476c657474657273516e756d657269635f6d6f64696669657273404a7375625f7261726974794562617369634a7375625f6c656e677468074e7375625f63686172616374657273476c657474657273557375625f6e756d657269635f6d6f64696669657273404b68616e646c655f74797065517669727475616c5f73756268616e646c654776657273696f6e0101a94e7374616e646172645f696d6167655838697066733a2f2f7a623272686b52636a5471546e5a387462704635485a474e4c4e355473324554633558477039576264614b41513433547246706f7274616c404864657369676e65724047736f6369616c73404676656e646f72404764656661756c7400536c6173745f7570646174655f616464726573735839007ad324c4fb08709dd997f6b2ba7980d5007103a2aa3f7a7eb8b44bc6f1a8e379127b811583070faf74db00d880d45027fe6171b1b69bd9ca4c76616c6964617465645f6279581c4da965a049dfd15ed1ee19fba6e2974a0b79fc416dd1796a1f97f5e1527265736f6c7665645f616464726573736573a1436164615839007ad324c4fb08709dd997f6b2ba7980d5007103a2aa3f7a7eb8b44bc6f1a8e379127b811583070faf74db00d880d45027fe6171b1b69bd9caff'
)
).toCore();

export type HandleMetadata = {
[policyId: string]: {
[handleName: string]: {
Expand Down Expand Up @@ -72,7 +93,7 @@ export const createHandlePolicy = async (keyAgent: KeyAgent) => {
return { policyId, policyScript, policySigner };
};

export const handleNames: Handle[] = ['handle1', 'handle2'];
export const handleNames: Handle[] = ['handle1', 'handle2', 'handl', 'sub@handl', 'virtual@handl'];

export const getHandlePolicyId = async (pathToSdkIpc: string): Promise<Cardano.PolicyId> => {
const handleProviderPolicyId = (await readFile(path.join(pathToSdkIpc, 'handle_policy_ids')))
Expand All @@ -88,7 +109,9 @@ export const mint = async (
keyAgent: KeyAgent,
tokens: Cardano.TokenMap,
txMetadatum: Cardano.Metadatum,
datum?: Cardano.PlutusData
datum?: Cardano.PlutusData,
isNFTHandle = true
// eslint-disable-next-line max-params
) => {
const knownAddresses = await firstValueFrom(wallet.addresses$);
const [{ address }] = knownAddresses;
Expand All @@ -98,19 +121,23 @@ export const mint = async (
blob: new Map([[721n, txMetadatum]])
};

const outputs: Set<Cardano.TxOut> = new Set();

if (isNFTHandle) {
outputs.add({
address,
datum,
value: {
assets: tokens,
coins: coinsRequiredByHandleMint
}
});
}

const txProps: InitializeTxProps = {
auxiliaryData,
mint: tokens,
outputs: new Set([
{
address,
datum,
value: {
assets: tokens,
coins: coinsRequiredByHandleMint
}
}
]),
outputs,
witness: { extraSigners: [policySigner], scripts: [policyScript] }
};

Expand All @@ -131,7 +158,7 @@ export const mintCIP25andCIP68Handles = async (
keyAgent: KeyAgent,
policyId: Cardano.PolicyId
) => {
const [cip25handle, cip68handle] = handleNames;
const [cip25handle, cip68handle, parentHandle, subHandle, virtualHandle] = handleNames;
const decodedCIP68HandleAssetName = Cardano.AssetName(util.utf8ToHex(cip68handle));
const cip68UserTokenAssetId = Cardano.AssetId.fromParts(
policyId,
Expand All @@ -141,6 +168,25 @@ export const mintCIP25andCIP68Handles = async (
policyId,
Asset.AssetNameLabel.encode(decodedCIP68HandleAssetName, Asset.AssetNameLabelNum.ReferenceNFT)
);

const decodedParentHandleAssetName = Cardano.AssetName(util.utf8ToHex(parentHandle));
const cip68ParentHandleAssetId = Cardano.AssetId.fromParts(
policyId,
Asset.AssetNameLabel.encode(decodedParentHandleAssetName, Asset.AssetNameLabelNum.UserNFT)
);

const decodedSubHandleAssetName = Cardano.AssetName(util.utf8ToHex(subHandle));
const cip68SubHandleAssetId = Cardano.AssetId.fromParts(
policyId,
Asset.AssetNameLabel.encode(decodedSubHandleAssetName, Asset.AssetNameLabelNum.UserNFT)
);

const decodedVirtualHandleAssetName = Cardano.AssetName(util.utf8ToHex(virtualHandle));
const cip68VirtualHandleAssetId = Cardano.AssetId.fromParts(
policyId,
Asset.AssetNameLabel.encode(decodedVirtualHandleAssetName, Asset.AssetNameLabelNum.VirtualHandle)
);

const cip25AssetId = Cardano.AssetId.fromParts(policyId, Cardano.AssetName(util.utf8ToHex(cip25handle)));
const tokens = new Map([
[cip25AssetId, 1n],
Expand All @@ -149,4 +195,19 @@ export const mintCIP25andCIP68Handles = async (
]);
const txMetadatum = metadatum.jsonToMetadatum(createHandleMetadata(policyId, [cip25handle]));
await mint(wallet, keyAgent, tokens, txMetadatum, handleDatum);

const parentHandleTxMetadatum = metadatum.jsonToMetadatum(createHandleMetadata(policyId, [parentHandle]));
const subHandleTxMetadatum = metadatum.jsonToMetadatum(createHandleMetadata(policyId, [subHandle]));
const virtualHandleTxMetadatum = metadatum.jsonToMetadatum(createHandleMetadata(policyId, [virtualHandle]));

await mint(wallet, keyAgent, new Map([[cip68ParentHandleAssetId, 1n]]), parentHandleTxMetadatum, handlDatum);
await mint(wallet, keyAgent, new Map([[cip68SubHandleAssetId, 1n]]), subHandleTxMetadatum, subhandlDatum);
await mint(
wallet,
keyAgent,
new Map([[cip68VirtualHandleAssetId, 1n]]),
virtualHandleTxMetadatum,
virtualhandlDatum,
false
);
};
8 changes: 4 additions & 4 deletions packages/e2e/test/wallet/PersonalWallet/handle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ describe('Ada handle', () => {
await mintCIP25andCIP68Handles(wallet, keyAgent, policyId);
let utxo = await firstValueFrom(wallet.balance.utxo.available$);
let receivingUtxo = await firstValueFrom(receivingWallet.balance.utxo.available$);
expect(utxo.assets?.size).toEqual(3);
expect(utxo.assets?.size).toEqual(6);
expect(receivingUtxo.assets).toBeUndefined();
let handles = await firstValueFrom(wallet.handles$);
let receivingHandles = await firstValueFrom(receivingWallet.handles$);
expect(handles.length).toEqual(2);
expect(handles.length).toEqual(4);
expect(receivingHandles.length).toEqual(0);

// send handle to another wallet
Expand All @@ -147,12 +147,12 @@ describe('Ada handle', () => {

utxo = await firstValueFrom(wallet.balance.utxo.available$);
receivingUtxo = await firstValueFrom(receivingWallet.balance.utxo.available$);
expect(utxo.assets?.size).toEqual(2);
expect(utxo.assets?.size).toEqual(5);
expect(receivingUtxo.assets?.size).toEqual(1);
expect(receivingUtxo.assets?.keys().next().value).toEqual(cip25AssetIds[0]);
handles = await firstValueFrom(wallet.handles$);
receivingHandles = await firstValueFrom(receivingWallet.handles$);
expect(handles.length).toEqual(1);
expect(handles.length).toEqual(3);
expect(receivingHandles.length).toEqual(1);

// send ada using handle
Expand Down
5 changes: 4 additions & 1 deletion packages/projection-typeorm/src/entity/Handle.entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AssetEntity } from './Asset.entity';
import { Cardano, Handle } from '@cardano-sdk/core';
import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm';
import { Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryColumn } from 'typeorm';

@Entity()
export class HandleEntity {
Expand All @@ -21,4 +21,7 @@ export class HandleEntity {
@Column('varchar', { nullable: true })
/** `null` when cardanoAddress === `null` */
defaultForPaymentCredential?: Handle | null;
@ManyToOne(() => HandleEntity, { nullable: true, onDelete: 'CASCADE' })
@JoinColumn()
parentHandle?: HandleEntity;
}
6 changes: 4 additions & 2 deletions packages/projection-typeorm/src/operators/storeHandles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const getSupply = async (queryRunner: QueryRunner, assetId: Cardano.AssetId) =>
export interface DefaultHandleParamsQueryResponse {
handle: NonNullable<HandleEntity['handle']>;
og: HandleMetadataEntity['og'];
parent_handle_handle?: HandleEntity['handle'] | null;
sameStakeCredential: boolean;
samePaymentCredential: boolean;
firstMintSlot: Cardano.Slot;
Expand Down Expand Up @@ -90,7 +91,7 @@ export const queryHandlesByAddressCredentials = (
.leftJoin('handle_metadata', 'm', 'm.handle=h.handle')
.innerJoin('asset', 'asset', 'asset.id=h.asset_id')
.select(
`h.handle, COALESCE(m.og, FALSE) as og,
`h.handle, h.parent_handle_handle, COALESCE(m.og, FALSE) as og,
asset.first_mint_block_slot as "firstMintSlot",
COALESCE((input_address.stake_credential_hash=a.stake_credential_hash), FALSE) as "sameStakeCredential",
(input_address.payment_credential_hash=a.payment_credential_hash) as "samePaymentCredential"`
Expand Down Expand Up @@ -173,7 +174,7 @@ const getDefaultInWalletAndUpdateOtherHandles = async (
const rollForward = async ({ handles, queryRunner, handleMetadata }: HandleEventParams) => {
const handleRepository = queryRunner.manager.getRepository(HandleEntity);

for (const { assetId, handle, policyId, latestOwnerAddress, datum, totalSupply } of handles) {
for (const { assetId, handle, policyId, latestOwnerAddress, datum, totalSupply, parentHandle } of handles) {
if (totalSupply === 1n) {
// if !address then it's burning it, otherwise transferring
const { cardanoAddress, hasDatum } = latestOwnerAddress
Expand All @@ -189,6 +190,7 @@ const rollForward = async ({ handles, queryRunner, handleMetadata }: HandleEvent
cardanoAddress,
handle,
hasDatum,
parentHandle,
policyId,
...defaultInWallet
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,27 +257,31 @@ describe('storeHandles', () => {
firstMintSlot: block.slot,
handle: 'sameaddress',
og: false,
parent_handle_handle: null,
samePaymentCredential: true,
sameStakeCredential: false
},
{
firstMintSlot: block.slot,
handle: 'samepayment',
og: false,
parent_handle_handle: null,
samePaymentCredential: true,
sameStakeCredential: false
},
{
firstMintSlot: block.slot,
handle: 'samepayment2',
og: false,
parent_handle_handle: null,
samePaymentCredential: true,
sameStakeCredential: false
},
{
firstMintSlot: block.slot,
handle: 'samepaymententerprise',
og: false,
parent_handle_handle: null,
samePaymentCredential: true,
sameStakeCredential: false
}
Expand All @@ -294,34 +298,39 @@ describe('storeHandles', () => {
firstMintSlot: block.slot,
handle: 'sameaddress',
og: false,
parent_handle_handle: null,
samePaymentCredential: true,
sameStakeCredential: true
},
{
firstMintSlot: block.slot,
handle: 'samepayment',
og: false,
parent_handle_handle: null,
samePaymentCredential: true,
sameStakeCredential: false
},
{
firstMintSlot: block.slot,
handle: 'samepayment2',
og: false,
parent_handle_handle: null,
samePaymentCredential: true,
sameStakeCredential: false
},
{
firstMintSlot: block.slot,
handle: 'samepaymententerprise',
og: false,
parent_handle_handle: null,
samePaymentCredential: true,
sameStakeCredential: false
},
{
firstMintSlot: block.slot,
handle: 'samestake',
og: true,
parent_handle_handle: null,
samePaymentCredential: false,
sameStakeCredential: true
}
Expand Down
Loading

0 comments on commit 3fa3920

Please sign in to comment.