Skip to content

Commit fb8aefc

Browse files
committed
feat: fee token in tx template
1 parent b364bb3 commit fb8aefc

File tree

12 files changed

+1275
-108
lines changed

12 files changed

+1275
-108
lines changed

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

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ 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 {
15+
NATIVE_TOKEN_UID,
16+
TOKEN_AUTHORITY_MASK,
17+
TOKEN_MELT_MASK,
18+
TOKEN_MINT_MASK,
19+
} from '../../../../src/constants';
20+
import { TokenVersion } from '../../../../src/types';
1521

1622
const DEBUG = true;
1723

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

0 commit comments

Comments
 (0)