Skip to content

Commit 4b2a00c

Browse files
feat(dapp): renew name with coupon test (#1005)
* feat(dapp): renew name with coupon test * uncomment * feat(dapp): create coupon using ptb --------- Co-authored-by: msarcev <mario.sarcevic@iota.org>
1 parent 78682cf commit 4b2a00c

File tree

2 files changed

+92
-2
lines changed

2 files changed

+92
-2
lines changed

dapp/tests/management/management.spec.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { resolve } from 'path';
55
import { normalizeIotaName } from '@iota/iota-names-sdk';
66
import { Ed25519Keypair } from '@iota/iota-sdk/keypairs/ed25519';
7-
import { formatAddress } from '@iota/iota-sdk/utils';
7+
import { formatAddress, NANOS_PER_IOTA } from '@iota/iota-sdk/utils';
88

99
import { formatDate } from '@/lib/utils/format/formatDate';
1010

@@ -14,6 +14,7 @@ import {
1414
addSubnameName,
1515
connectName,
1616
connectWallet,
17+
createCoupon,
1718
createWallet,
1819
editSetup,
1920
generateRandomName,
@@ -658,4 +659,49 @@ test.describe.parallel('Name Management Tests', () => {
658659

659660
await page.close();
660661
});
662+
663+
test('Renew name with coupon', async ({ appPage: page, context, sharedState }) => {
664+
const keypair = Ed25519Keypair.deriveKeypair(sharedState.wallet.mnemonic ?? '');
665+
const name = generateRandomName('renewcoupon');
666+
667+
const responsePurchase = await purchaseName(name, keypair);
668+
expect(responsePurchase.effects?.status.status).toBe('success');
669+
670+
const couponCode = 'FIXED_1000';
671+
await createCoupon({
672+
code: couponCode,
673+
type: 'fixed',
674+
value: BigInt(1000) * NANOS_PER_IOTA,
675+
});
676+
677+
await page.goto('/my-names');
678+
await expect(
679+
page.getByTestId('name-card').filter({ hasText: normalizeIotaName(name, 'at') }),
680+
).toBeVisible({ timeout: 10_000 });
681+
682+
const nameCard = page
683+
.getByTestId('name-card')
684+
.filter({ hasText: normalizeIotaName(name, 'at') });
685+
await nameCard.getByTestId('name-card-avatar').hover();
686+
const menuButtonLocator = nameCard.getByTestId('menu-button');
687+
await expect(menuButtonLocator).toBeVisible();
688+
await menuButtonLocator.click();
689+
await page.getByText('Renew Name', { exact: true }).click();
690+
const dialog = page.getByRole('dialog');
691+
await expect(dialog.getByText('Renew Name', { exact: true })).toBeVisible();
692+
693+
await dialog.getByPlaceholder('Have a discount code?').fill(couponCode);
694+
await dialog.getByText('+ Apply Coupon').click();
695+
await expect(dialog.getByText(couponCode, { exact: true })).toBeVisible();
696+
697+
await dialog.getByRole('button', { name: 'Renew' }).click();
698+
(await context.waitForEvent('page')).getByRole('button', { name: 'Approve' }).click();
699+
await page.bringToFront();
700+
701+
await expect(page.getByText('Name renewed successfully', { exact: false })).toBeVisible({
702+
timeout: 30_000,
703+
});
704+
705+
await page.close();
706+
});
661707
});

dapp/tests/utils.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import type { BrowserContext, Page } from '@playwright/test';
77

88
import 'dotenv/config';
99

10-
import { execFileSync } from 'child_process';
10+
import { execFileSync, execSync } from 'child_process';
1111
import { IotaNamesTransaction, isSubname, NameRecord } from '@iota/iota-names-sdk';
1212
import type { Signer } from '@iota/iota-sdk/cryptography';
1313
import { Transaction } from '@iota/iota-sdk/transactions';
1414
import { NANOS_PER_IOTA } from '@iota/iota-sdk/utils';
15+
import { blake2b } from '@noble/hashes/blake2';
16+
import { bytesToHex } from '@noble/hashes/utils';
1517

1618
import { buildCreateAuctionTransaction, buildPlaceBidTransaction } from '@/auctions';
1719
import { CONFIG } from '@/config';
@@ -320,6 +322,7 @@ export async function setAvatar(nameRecord: NameRecord, signer: Signer) {
320322
console.log(`Avatar set to address: ${address}`);
321323
return responseSetAvatar;
322324
}
325+
323326
export function deriveAddressFromMnemonic(mnemonic: string, path?: string) {
324327
const keypair = Ed25519Keypair.deriveKeypair(mnemonic, path);
325328
const address = keypair.getPublicKey().toIotaAddress();
@@ -416,3 +419,44 @@ export function generateRandomSubname(subname: string, parentName: string) {
416419
const random = Math.floor(Math.random() * 10_000);
417420
return `${subname}${random}.${parentName}`;
418421
}
422+
423+
interface CreateCouponOptions {
424+
code: string;
425+
type: 'fixed' | 'percentage';
426+
value: bigint;
427+
}
428+
429+
export async function createCoupon({ code, type, value }: CreateCouponOptions) {
430+
const couponCodeHash = bytesToHex(blake2b(code, { dkLen: 32 }));
431+
432+
const couponsPackageId = iotaNamesClient.getPackage('couponsPackageId');
433+
const iotaNamesObjectId = iotaNamesClient.getPackage('iotaNamesObjectId');
434+
const adminCapId = iotaNamesClient.getPackage('adminCap');
435+
const adminAddress = iotaNamesClient.getPackage('adminAddress');
436+
437+
const functionName =
438+
type === 'fixed' ? 'admin_add_fixed_coupon' : 'admin_add_percentage_coupon';
439+
440+
const command = `iota client ptb \
441+
--move-call ${couponsPackageId}::rules::new_empty_rules \
442+
--assign empty_rules \
443+
--move-call ${couponsPackageId}::coupon_house::${functionName} \
444+
@${adminCapId} \
445+
@${iotaNamesObjectId} \
446+
'"${couponCodeHash}"' \
447+
${value.toString()} \
448+
empty_rules \
449+
--gas-budget 50000000 \
450+
--sender @${adminAddress}`;
451+
452+
console.log(`Creating ${type} coupon "${code}" with hash ${couponCodeHash}...`);
453+
454+
try {
455+
const output = execSync(command, { encoding: 'utf-8' });
456+
console.log(`Created ${type} coupon "${code}" successfully`);
457+
return output;
458+
} catch (error) {
459+
console.error(`Failed to create coupon "${code}":`, error);
460+
throw error;
461+
}
462+
}

0 commit comments

Comments
 (0)