Skip to content

Commit 825e5cb

Browse files
committed
feat: fee token in tx template
1 parent 60733a2 commit 825e5cb

File tree

12 files changed

+753
-121
lines changed

12 files changed

+753
-121
lines changed

__tests__/integration/hathorwallet_facade.test.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2403,15 +2403,9 @@ describe('mintTokens', () => {
24032403
const expectedAmount5 = expectedAmount4 + 100n;
24042404
expect(tokenBalance5[0]).toHaveProperty('balance.unlocked', expectedAmount5);
24052405

2406-
const dataOutput5 = mintResponse5.outputs.filter(
2407-
o => o.getType(hWallet.getNetworkObject()) === 'data'
2408-
);
2409-
expect(dataOutput5).toHaveLength(1);
2410-
expect(dataOutput5[0]).toHaveProperty('value', 1n);
2411-
expect(dataOutput5[0]).toHaveProperty(
2412-
'script',
2413-
Buffer.from([6, 102, 111, 111, 98, 97, 114, 172])
2414-
);
2406+
const dataOutput5 = mintResponse5.outputs[mintResponse5.outputs.length - 1];
2407+
expect(dataOutput5).toHaveProperty('value', 1n);
2408+
expect(dataOutput5).toHaveProperty('script', Buffer.from([6, 102, 111, 111, 98, 97, 114, 172]));
24152409

24162410
const mintResponse6 = await hWallet.mintTokens(tokenUid, 100n, {
24172411
unshiftData: true,

__tests__/integration/template/transaction/template.test.ts

Lines changed: 177 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import SendTransaction from '../../../../src/new/sendTransaction';
1111
import transactionUtils from '../../../../src/utils/transaction';
1212
import { TransactionTemplateBuilder } from '../../../../src/template/transaction/builder';
1313
import { WalletTxTemplateInterpreter } from '../../../../src/template/transaction/interpreter';
14-
import { TOKEN_AUTHORITY_MASK, TOKEN_MELT_MASK, TOKEN_MINT_MASK } from '../../../../src/constants';
14+
import { NATIVE_TOKEN_UID, TOKEN_AUTHORITY_MASK, TOKEN_MELT_MASK, TOKEN_MINT_MASK } from '../../../../src/constants';
15+
import { TokenVersion } from '../../../../src/types';
1516

1617
const DEBUG = true;
1718

@@ -438,3 +439,178 @@ describe('Template execution', () => {
438439
);
439440
});
440441
});
442+
443+
describe('Template execution with fee tokens', () => {
444+
let hWallet: HathorWallet;
445+
let interpreter: WalletTxTemplateInterpreter;
446+
let tokenUid: string;
447+
448+
beforeAll(async () => {
449+
hWallet = await generateWalletHelper(null);
450+
interpreter = new WalletTxTemplateInterpreter(hWallet);
451+
const address = await hWallet.getAddressAtIndex(0);
452+
await GenesisWalletHelper.injectFunds(hWallet, address, 10n, {});
453+
});
454+
455+
afterAll(async () => {
456+
await hWallet.stop();
457+
await stopAllWallets();
458+
await GenesisWalletHelper.clearListeners();
459+
});
460+
461+
it('should be not able to create a custom fee token without providing a fee header', async () => {
462+
const template = new TransactionTemplateBuilder()
463+
.addConfigAction({
464+
createToken: true,
465+
tokenName: 'Tmpl Test Fee Token 01',
466+
tokenSymbol: 'TTT01',
467+
tokenVersion: TokenVersion.FEE,
468+
})
469+
.addSetVarAction({ name: 'addr', call: { method: 'get_wallet_address' } })
470+
.addUtxoSelect({ fill: 5 })
471+
.addTokenOutput({ address: '{addr}', amount: 100, useCreatedToken: true })
472+
.addTokenOutput({ address: '{addr}', amount: 100, useCreatedToken: true })
473+
.addTokenOutput({ address: '{addr}', amount: 100, useCreatedToken: true })
474+
.addTokenOutput({ address: '{addr}', amount: 100, useCreatedToken: true })
475+
.build();
476+
477+
const tx = await interpreter.build(template, DEBUG);
478+
479+
expect(tx.outputs).toHaveLength(5);
480+
481+
// HTR change
482+
expect(tx.outputs[0].tokenData).toBe(0);
483+
expect(tx.outputs[0].value).toBe(5n);
484+
485+
// Created token
486+
expect(tx.outputs[1].tokenData).toBe(1);
487+
expect(tx.outputs[1].value).toBe(100n);
488+
489+
expect(tx.outputs[2].tokenData).toBe(1);
490+
expect(tx.outputs[2].value).toBe(100n);
491+
492+
expect(tx.outputs[3].tokenData).toBe(1);
493+
expect(tx.outputs[3].value).toBe(100n);
494+
495+
expect(tx.outputs[4].tokenData).toBe(1);
496+
expect(tx.outputs[4].value).toBe(100n);
497+
498+
// Not have a fee header
499+
expect(tx.headers).toHaveLength(0);
500+
501+
await transactionUtils.signTransaction(tx, hWallet.storage, DEFAULT_PIN_CODE);
502+
tx.prepareToSend();
503+
const sendTx = new SendTransaction({ storage: hWallet.storage, transaction: tx });
504+
505+
// Expecting the transaction to fail validation with negative HTR balance
506+
await expect(sendTx.runFromMining()).rejects.toThrow(
507+
'full validation failed: HTR balance is different than expected. (amount=-5, expected=0)'
508+
);
509+
510+
// Check the wallet balance to verify nothing was spent from the wallet
511+
const htrBalance = await hWallet.getBalance(NATIVE_TOKEN_UID);
512+
expect(htrBalance[0]).toHaveProperty('balance.unlocked', 10n);
513+
});
514+
515+
it('should be able to create a custom fee token providing a fee header', async () => {
516+
const mintAmount = 8582n;
517+
const template = new TransactionTemplateBuilder()
518+
.addConfigAction({
519+
createToken: true,
520+
tokenName: 'Tmpl Test Fee Token 01',
521+
tokenSymbol: 'TTT01',
522+
tokenVersion: TokenVersion.FEE,
523+
})
524+
.addSetVarAction({ name: 'addr', call: { method: 'get_wallet_address' } })
525+
.addUtxoSelect({ fill: 1 })
526+
.addTokenOutput({ address: '{addr}', amount: mintAmount, useCreatedToken: true })
527+
.addFee({ amount: 1 })
528+
.build();
529+
530+
const tx = await interpreter.build(template, DEBUG);
531+
532+
expect(tx.outputs).toHaveLength(2);
533+
534+
// HTR change
535+
expect(tx.outputs[0].tokenData).toBe(0);
536+
expect(tx.outputs[0].value).toBe(9n);
537+
538+
// Created token
539+
expect(tx.outputs[1].tokenData).toBe(1);
540+
expect(tx.outputs[1].value).toBe(mintAmount);
541+
542+
// Have a fee header
543+
expect(tx.headers).toHaveLength(1);
544+
expect(tx.getFeeHeader()).not.toBeNull();
545+
expect(tx.getFeeHeader()!.entries).toHaveLength(1);
546+
expect(tx.getFeeHeader()!.entries[0].tokenIndex).toBe(0);
547+
expect(tx.getFeeHeader()!.entries[0].amount).toBe(1n);
548+
549+
// after validate send the tx to the mining service
550+
await transactionUtils.signTransaction(tx, hWallet.storage, DEFAULT_PIN_CODE);
551+
tx.prepareToSend();
552+
const sendTx = new SendTransaction({ storage: hWallet.storage, transaction: tx });
553+
await sendTx.runFromMining();
554+
expect(tx.hash).not.toBeNull();
555+
if (tx.hash === null) {
556+
throw new Error('Transaction does not have a hash');
557+
}
558+
tokenUid = tx.hash;
559+
await waitForTxReceived(hWallet, tx.hash, undefined);
560+
561+
const htrBalance = await hWallet.getBalance(NATIVE_TOKEN_UID);
562+
expect(htrBalance[0]).toHaveProperty('balance.unlocked', 9n);
563+
});
564+
it('should be able to complete the fees of a transaction', async () => {
565+
const template = new TransactionTemplateBuilder()
566+
.addConfigAction({
567+
createToken: true,
568+
tokenName: 'Tmpl Test Fee Token 01',
569+
tokenSymbol: 'TTT01',
570+
tokenVersion: TokenVersion.FEE,
571+
})
572+
.addSetVarAction({ name: 'addr', call: { method: 'get_wallet_address' } })
573+
.addUtxoSelect({ fill: 5 })
574+
.addTokenOutput({ address: '{addr}', amount: 9999999, useCreatedToken: true })
575+
.addTokenOutput({ address: '{addr}', amount: 9999999, useCreatedToken: true })
576+
.addTokenOutput({ address: '{addr}', amount: 9999999, useCreatedToken: true })
577+
.addTokenOutput({ address: '{addr}', amount: 9999999, useCreatedToken: true })
578+
.addCompleteAction({ calculateFee: true, token: '00' })
579+
.build();
580+
581+
const tx = await interpreter.build(template, DEBUG);
582+
583+
// check the outputs
584+
expect(tx.outputs).toHaveLength(6);
585+
586+
// HTR change
587+
expect(tx.outputs[0].tokenData).toBe(0);
588+
expect(tx.outputs[0].value).toBe(4n);
589+
590+
// Created token
591+
expect(tx.outputs[1].tokenData).toBe(1);
592+
expect(tx.outputs[1].value).toBe(9999999n);
593+
594+
// Have a fee header
595+
expect(tx.headers).toHaveLength(1);
596+
expect(tx.getFeeHeader()).not.toBeNull();
597+
expect(tx.getFeeHeader()!.entries).toHaveLength(1);
598+
expect(tx.getFeeHeader()!.entries[0].tokenIndex).toBe(0);
599+
expect(tx.getFeeHeader()!.entries[0].amount).toBe(4n);
600+
601+
// after validate send the tx to the mining service
602+
await transactionUtils.signTransaction(tx, hWallet.storage, DEFAULT_PIN_CODE);
603+
tx.prepareToSend();
604+
const sendTx = new SendTransaction({ storage: hWallet.storage, transaction: tx });
605+
await sendTx.runFromMining();
606+
expect(tx.hash).not.toBeNull();
607+
if (tx.hash === null) {
608+
throw new Error('Transaction does not have a hash');
609+
}
610+
611+
await waitForTxReceived(hWallet, tx.hash, undefined);
612+
613+
const htrBalance = await hWallet.getBalance(NATIVE_TOKEN_UID);
614+
expect(htrBalance[0]).toHaveProperty('balance.unlocked', 5n);
615+
});
616+
});

0 commit comments

Comments
 (0)