Changeset View
Changeset View
Standalone View
Standalone View
modules/ecash-wallet/src/wallet.test.ts
| Show All 14 Lines | import { | ||||
| OP_RETURN, | OP_RETURN, | ||||
| GenesisInfo, | GenesisInfo, | ||||
| SLP_MAX_SEND_OUTPUTS, | SLP_MAX_SEND_OUTPUTS, | ||||
| COINBASE_MATURITY, | COINBASE_MATURITY, | ||||
| ALP_POLICY_MAX_OUTPUTS, | ALP_POLICY_MAX_OUTPUTS, | ||||
| payment, | payment, | ||||
| SLP_TOKEN_TYPE_FUNGIBLE, | SLP_TOKEN_TYPE_FUNGIBLE, | ||||
| ALP_TOKEN_TYPE_STANDARD, | ALP_TOKEN_TYPE_STANDARD, | ||||
| SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| } from 'ecash-lib'; | } from 'ecash-lib'; | ||||
| import { | import { | ||||
| OutPoint, | OutPoint, | ||||
| ScriptUtxo, | ScriptUtxo, | ||||
| ChronikClient, | ChronikClient, | ||||
| Token, | Token, | ||||
| TokenType, | TokenType, | ||||
| } from 'chronik-client'; | } from 'chronik-client'; | ||||
| ▲ Show 20 Lines • Show All 54 Lines • ▼ Show 20 Lines | const DUMMY_SPENDABLE_COINBASE_UTXO: ScriptUtxo = { | ||||
| outpoint: { ...DUMMY_OUTPOINT, outIdx: 2 }, | outpoint: { ...DUMMY_OUTPOINT, outIdx: 2 }, | ||||
| blockHeight: DUMMY_TIPHEIGHT - COINBASE_MATURITY, | blockHeight: DUMMY_TIPHEIGHT - COINBASE_MATURITY, | ||||
| }; | }; | ||||
| // Dummy ALP STANDARD utxos (quantity and mintbaton) | // Dummy ALP STANDARD utxos (quantity and mintbaton) | ||||
| const DUMMY_TOKENID_ALP_TOKEN_TYPE_STANDARD = toHex(strToBytes('SLP2')).repeat( | const DUMMY_TOKENID_ALP_TOKEN_TYPE_STANDARD = toHex(strToBytes('SLP2')).repeat( | ||||
| 8, | 8, | ||||
| ); | ); | ||||
| const TOKEN_TYPE_ALP_TOKEN_TYPE_STANDARD: TokenType = { | |||||
| protocol: 'ALP', | |||||
| type: 'ALP_TOKEN_TYPE_STANDARD', | |||||
| number: 0, | |||||
| }; | |||||
| const ALP_TOKEN_TYPE_STANDARD_ATOMS = 101n; | const ALP_TOKEN_TYPE_STANDARD_ATOMS = 101n; | ||||
| const DUMMY_TOKEN_ALP_TOKEN_TYPE_STANDARD: Token = { | const DUMMY_TOKEN_ALP_TOKEN_TYPE_STANDARD: Token = { | ||||
| tokenId: DUMMY_TOKENID_ALP_TOKEN_TYPE_STANDARD, | tokenId: DUMMY_TOKENID_ALP_TOKEN_TYPE_STANDARD, | ||||
| tokenType: TOKEN_TYPE_ALP_TOKEN_TYPE_STANDARD, | tokenType: ALP_TOKEN_TYPE_STANDARD, | ||||
| atoms: ALP_TOKEN_TYPE_STANDARD_ATOMS, | atoms: ALP_TOKEN_TYPE_STANDARD_ATOMS, | ||||
| isMintBaton: false, | isMintBaton: false, | ||||
| }; | }; | ||||
| const DUMMY_TOKEN_UTXO_ALP_TOKEN_TYPE_STANDARD: ScriptUtxo = { | const DUMMY_TOKEN_UTXO_ALP_TOKEN_TYPE_STANDARD: ScriptUtxo = { | ||||
| ...DUMMY_UTXO, | ...DUMMY_UTXO, | ||||
| outpoint: { ...DUMMY_OUTPOINT, outIdx: 3 }, | outpoint: { ...DUMMY_OUTPOINT, outIdx: 3 }, | ||||
| token: DUMMY_TOKEN_ALP_TOKEN_TYPE_STANDARD, | token: DUMMY_TOKEN_ALP_TOKEN_TYPE_STANDARD, | ||||
| }; | }; | ||||
| Show All 16 Lines | return { | ||||
| token: { ...DUMMY_TOKEN_ALP_TOKEN_TYPE_STANDARD, tokenId, atoms }, | token: { ...DUMMY_TOKEN_ALP_TOKEN_TYPE_STANDARD, tokenId, atoms }, | ||||
| }; | }; | ||||
| }; | }; | ||||
| // Dummy SLP utxos (quantity and mintbaton) | // Dummy SLP utxos (quantity and mintbaton) | ||||
| const DUMMY_TOKENID_SLP_TOKEN_TYPE_FUNGIBLE = toHex(strToBytes('SLP0')).repeat( | const DUMMY_TOKENID_SLP_TOKEN_TYPE_FUNGIBLE = toHex(strToBytes('SLP0')).repeat( | ||||
| 8, | 8, | ||||
| ); | ); | ||||
| const TOKEN_TYPE_SLP_TOKEN_TYPE_FUNGIBLE: TokenType = { | |||||
| protocol: 'SLP', | |||||
| type: 'SLP_TOKEN_TYPE_FUNGIBLE', | |||||
| number: 1, | |||||
| }; | |||||
| const SLP_TOKEN_TYPE_FUNGIBLE_ATOMS = 100n; | const SLP_TOKEN_TYPE_FUNGIBLE_ATOMS = 100n; | ||||
| const DUMMY_TOKEN_SLP_TOKEN_TYPE_FUNGIBLE: Token = { | const DUMMY_TOKEN_SLP_TOKEN_TYPE_FUNGIBLE: Token = { | ||||
| tokenId: DUMMY_TOKENID_SLP_TOKEN_TYPE_FUNGIBLE, | tokenId: DUMMY_TOKENID_SLP_TOKEN_TYPE_FUNGIBLE, | ||||
| tokenType: TOKEN_TYPE_SLP_TOKEN_TYPE_FUNGIBLE, | tokenType: SLP_TOKEN_TYPE_FUNGIBLE, | ||||
| atoms: SLP_TOKEN_TYPE_FUNGIBLE_ATOMS, | atoms: SLP_TOKEN_TYPE_FUNGIBLE_ATOMS, | ||||
| isMintBaton: false, | isMintBaton: false, | ||||
| }; | }; | ||||
| const DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_FUNGIBLE: ScriptUtxo = { | const DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_FUNGIBLE: ScriptUtxo = { | ||||
| ...DUMMY_UTXO, | ...DUMMY_UTXO, | ||||
| outpoint: { ...DUMMY_OUTPOINT, outIdx: 5 }, | outpoint: { ...DUMMY_OUTPOINT, outIdx: 5 }, | ||||
| token: DUMMY_TOKEN_SLP_TOKEN_TYPE_FUNGIBLE, | token: DUMMY_TOKEN_SLP_TOKEN_TYPE_FUNGIBLE, | ||||
| }; | }; | ||||
| const getDummySlpUtxo = ( | const getDummySlpUtxo = ( | ||||
| atoms: bigint, | atoms: bigint, | ||||
| tokenId = DUMMY_TOKENID_SLP_TOKEN_TYPE_FUNGIBLE, | tokenId = DUMMY_TOKENID_SLP_TOKEN_TYPE_FUNGIBLE, | ||||
| ) => { | ) => { | ||||
| return { | return { | ||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_FUNGIBLE, | ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_FUNGIBLE, | ||||
| token: { ...DUMMY_TOKEN_SLP_TOKEN_TYPE_FUNGIBLE, tokenId, atoms }, | token: { ...DUMMY_TOKEN_SLP_TOKEN_TYPE_FUNGIBLE, tokenId, atoms }, | ||||
| }; | }; | ||||
| }; | }; | ||||
| // Dummy SLP_TOKEN_TYPE_MINT_VAULT utxos (quantity only; mint batons do not exist for this type) | |||||
| const DUMMY_TOKENID_SLP_TOKEN_TYPE_MINT_VAULT = toHex( | |||||
| strToBytes('SLP2'), | |||||
| ).repeat(8); | |||||
| const SLP_TOKEN_TYPE_MINT_VAULT_ATOMS = 100n; | |||||
| const DUMMY_TOKEN_SLP_TOKEN_TYPE_MINT_VAULT: Token = { | |||||
| tokenId: DUMMY_TOKENID_SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| tokenType: SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| atoms: SLP_TOKEN_TYPE_MINT_VAULT_ATOMS, | |||||
| isMintBaton: false, | |||||
| }; | |||||
| const DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT: ScriptUtxo = { | |||||
| ...DUMMY_UTXO, | |||||
| outpoint: { ...DUMMY_OUTPOINT, outIdx: 5 }, | |||||
| token: DUMMY_TOKEN_SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| }; | |||||
| const DUMMY_SPENDABLE_COINBASE_UTXO_TOKEN: ScriptUtxo = { | const DUMMY_SPENDABLE_COINBASE_UTXO_TOKEN: ScriptUtxo = { | ||||
| ...DUMMY_SPENDABLE_COINBASE_UTXO, | ...DUMMY_SPENDABLE_COINBASE_UTXO, | ||||
| outpoint: { ...DUMMY_OUTPOINT, outIdx: 5 }, | outpoint: { ...DUMMY_OUTPOINT, outIdx: 5 }, | ||||
| token: DUMMY_TOKEN_ALP_TOKEN_TYPE_STANDARD, | token: DUMMY_TOKEN_ALP_TOKEN_TYPE_STANDARD, | ||||
| }; | }; | ||||
| // Utxo set used in testing to show all utxo types supported by ecash-wallet | // Utxo set used in testing to show all utxo types supported by ecash-wallet | ||||
| const ALL_SUPPORTED_UTXOS: ScriptUtxo[] = [ | const ALL_SUPPORTED_UTXOS: ScriptUtxo[] = [ | ||||
| DUMMY_UTXO, | DUMMY_UTXO, | ||||
| DUMMY_UNSPENDABLE_COINBASE_UTXO, | DUMMY_UNSPENDABLE_COINBASE_UTXO, | ||||
| DUMMY_SPENDABLE_COINBASE_UTXO, | DUMMY_SPENDABLE_COINBASE_UTXO, | ||||
| DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_FUNGIBLE, | |||||
| DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| DUMMY_TOKEN_UTXO_ALP_TOKEN_TYPE_STANDARD, | DUMMY_TOKEN_UTXO_ALP_TOKEN_TYPE_STANDARD, | ||||
| DUMMY_TOKEN_UTXO_ALP_TOKEN_TYPE_STANDARD_MINTBATON, | DUMMY_TOKEN_UTXO_ALP_TOKEN_TYPE_STANDARD_MINTBATON, | ||||
| DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| DUMMY_SPENDABLE_COINBASE_UTXO_TOKEN, | DUMMY_SPENDABLE_COINBASE_UTXO_TOKEN, | ||||
| ]; | ]; | ||||
| const MOCK_DESTINATION_ADDRESS = Address.p2pkh('deadbeef'.repeat(5)).toString(); | const MOCK_DESTINATION_ADDRESS = Address.p2pkh('deadbeef'.repeat(5)).toString(); | ||||
| const MOCK_DESTINATION_SCRIPT = Address.fromCashAddress( | const MOCK_DESTINATION_SCRIPT = Address.fromCashAddress( | ||||
| MOCK_DESTINATION_ADDRESS, | MOCK_DESTINATION_ADDRESS, | ||||
| ).toScript(); | ).toScript(); | ||||
| ▲ Show 20 Lines • Show All 152 Lines • ▼ Show 20 Lines | it('Throw error on sync() fail', async () => { | ||||
| expect(errorWallet.utxos).to.deep.equal([]); | expect(errorWallet.utxos).to.deep.equal([]); | ||||
| }); | }); | ||||
| }); | }); | ||||
| describe('Support functions', () => { | describe('Support functions', () => { | ||||
| context('validateTokenActions', () => { | context('validateTokenActions', () => { | ||||
| const dummyGenesisAction = { | const dummyGenesisAction = { | ||||
| type: 'GENESIS', | type: 'GENESIS', | ||||
| tokenType: TOKEN_TYPE_ALP_TOKEN_TYPE_STANDARD, | tokenType: ALP_TOKEN_TYPE_STANDARD, | ||||
| genesisInfo: { | genesisInfo: { | ||||
| tokenTicker: 'ALP', | tokenTicker: 'ALP', | ||||
| tokenName: 'ALP Test Token', | tokenName: 'ALP Test Token', | ||||
| url: 'cashtab.com', | url: 'cashtab.com', | ||||
| decimals: 0, | decimals: 0, | ||||
| data: 'deadbeef', | data: 'deadbeef', | ||||
| }, | }, | ||||
| } as payment.GenesisAction; | } as payment.GenesisAction; | ||||
| const tokenOne = '11'.repeat(32); | const tokenOne = '11'.repeat(32); | ||||
| const tokenTwo = '22'.repeat(32); | const tokenTwo = '22'.repeat(32); | ||||
| const dummySendActionTokenOne = { | const dummySendActionTokenOne = { | ||||
| type: 'SEND', | type: 'SEND', | ||||
| tokenId: tokenOne, | tokenId: tokenOne, | ||||
| tokenType: ALP_TOKEN_TYPE_STANDARD, | |||||
| } as payment.SendAction; | } as payment.SendAction; | ||||
| const dummyMintActionTokenOne = { | const dummyMintActionTokenOne = { | ||||
| type: 'MINT', | type: 'MINT', | ||||
| tokenId: tokenOne, | tokenId: tokenOne, | ||||
| tokenType: ALP_TOKEN_TYPE_STANDARD, | |||||
| } as payment.MintAction; | } as payment.MintAction; | ||||
| const dummyBurnActionTokenOne = { | const dummyBurnActionTokenOne = { | ||||
| type: 'BURN', | type: 'BURN', | ||||
| tokenId: tokenOne, | tokenId: tokenOne, | ||||
| tokenType: ALP_TOKEN_TYPE_STANDARD, | |||||
| } as payment.BurnAction; | } as payment.BurnAction; | ||||
| it('An empty array at the tokenActions key is valid', () => { | it('An empty array at the tokenActions key is valid', () => { | ||||
| expect(() => validateTokenActions([])).not.to.throw(); | expect(() => validateTokenActions([])).not.to.throw(); | ||||
| }); | }); | ||||
| it('tokenActions with a genesisAction at index 0 are valid', () => { | it('tokenActions with a genesisAction at index 0 are valid', () => { | ||||
| expect(() => | expect(() => | ||||
| validateTokenActions([dummyGenesisAction]), | validateTokenActions([dummyGenesisAction]), | ||||
| ).not.to.throw(); | ).not.to.throw(); | ||||
| ▲ Show 20 Lines • Show All 107 Lines • ▼ Show 20 Lines | context('validateTokenActions', () => { | ||||
| dummyMintActionTokenOne, | dummyMintActionTokenOne, | ||||
| dummySendActionTokenOne, | dummySendActionTokenOne, | ||||
| ]), | ]), | ||||
| ).to.throw( | ).to.throw( | ||||
| Error, | Error, | ||||
| `ecash-wallet does not support minting and sending the same token in the same Action. tokenActions MINT and SEND ${tokenOne}.`, | `ecash-wallet does not support minting and sending the same token in the same Action. tokenActions MINT and SEND ${tokenOne}.`, | ||||
| ); | ); | ||||
| }); | }); | ||||
| it('tokenActions that call for MINT of a SLP_TOKEN_TYPE_MINT_VAULT token are invalid', () => { | |||||
| expect(() => | |||||
| validateTokenActions([ | |||||
| { | |||||
| ...dummyMintActionTokenOne, | |||||
| tokenType: SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| }, | |||||
| ]), | |||||
| ).to.throw( | |||||
| Error, | |||||
| `ecash-wallet does not currently support minting SLP_TOKEN_TYPE_MINT_VAULT tokens.`, | |||||
| ); | |||||
| }); | |||||
| }); | }); | ||||
| context('getActionTotals', () => { | context('getActionTotals', () => { | ||||
| it('Returns expected ActionTotal for a non-token action (i.e., sats only)', () => { | it('Returns expected ActionTotal for a non-token action (i.e., sats only)', () => { | ||||
| const action = { | const action = { | ||||
| outputs: [ | outputs: [ | ||||
| { | { | ||||
| sats: 0n, | sats: 0n, | ||||
| script: new Script(new Uint8Array([OP_RETURN])), | script: new Script(new Uint8Array([OP_RETURN])), | ||||
| ▲ Show 20 Lines • Show All 1,168 Lines • ▼ Show 20 Lines | context('getTokenType', () => { | ||||
| tokenActions: [ | tokenActions: [ | ||||
| { | { | ||||
| type: 'MINT', | type: 'MINT', | ||||
| tokenType: ALP_TOKEN_TYPE_STANDARD, | tokenType: ALP_TOKEN_TYPE_STANDARD, | ||||
| } as payment.MintAction, | } as payment.MintAction, | ||||
| ], | ], | ||||
| }; | }; | ||||
| expect(getTokenType(alpMintAction)).to.deep.equal( | expect(getTokenType(alpMintAction)).to.deep.equal( | ||||
| TOKEN_TYPE_ALP_TOKEN_TYPE_STANDARD, | ALP_TOKEN_TYPE_STANDARD, | ||||
| ); | ); | ||||
| }); | }); | ||||
| it('Returns an unsupported token type without throwing', () => { | it('Returns an unsupported token type without throwing', () => { | ||||
| const alpMintAction: payment.Action = { | const alpMintAction: payment.Action = { | ||||
| outputs: [ | outputs: [ | ||||
| /** Blank OP_RETURN at outIdx 0 */ | /** Blank OP_RETURN at outIdx 0 */ | ||||
| { sats: 0n }, | { sats: 0n }, | ||||
| /** Mint qty at outIdx 1 */ | /** Mint qty at outIdx 1 */ | ||||
| ▲ Show 20 Lines • Show All 492 Lines • ▼ Show 20 Lines | context('finalizeOutputs', () => { | ||||
| isMintBaton: false, | isMintBaton: false, | ||||
| }, | }, | ||||
| ], | ], | ||||
| tokenActions: [ | tokenActions: [ | ||||
| { | { | ||||
| type: 'SEND', | type: 'SEND', | ||||
| tokenId: tokenIdThisAction, | tokenId: tokenIdThisAction, | ||||
| tokenType: { | tokenType: { | ||||
| ...TOKEN_TYPE_SLP_TOKEN_TYPE_FUNGIBLE, | ...SLP_TOKEN_TYPE_FUNGIBLE, | ||||
| type: 'SOME_UNSUPPORTED_TYPE', | type: 'SOME_UNSUPPORTED_TYPE', | ||||
| } as unknown as TokenType, | } as unknown as TokenType, | ||||
| }, | }, | ||||
| ] as payment.TokenAction[], | ] as payment.TokenAction[], | ||||
| }, | }, | ||||
| [ | [ | ||||
| { | { | ||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_FUNGIBLE, | ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_FUNGIBLE, | ||||
| ▲ Show 20 Lines • Show All 127 Lines • ▼ Show 20 Lines | context('finalizeOutputs', () => { | ||||
| atoms: 1_000_000n, | atoms: 1_000_000n, | ||||
| script: DUMMY_SCRIPT, | script: DUMMY_SCRIPT, | ||||
| isMintBaton: false, | isMintBaton: false, | ||||
| }, | }, | ||||
| ], | ], | ||||
| tokenActions: [ | tokenActions: [ | ||||
| { | { | ||||
| type: 'GENESIS', | type: 'GENESIS', | ||||
| tokenType: | tokenType: SLP_TOKEN_TYPE_FUNGIBLE, | ||||
| TOKEN_TYPE_SLP_TOKEN_TYPE_FUNGIBLE, | |||||
| genesisInfo: nullGenesisInfo, | genesisInfo: nullGenesisInfo, | ||||
| }, | }, | ||||
| { | { | ||||
| type: 'SEND', | type: 'SEND', | ||||
| tokenId: '11'.repeat(32), | tokenId: '11'.repeat(32), | ||||
| tokenType: SLP_TOKEN_TYPE_FUNGIBLE, | tokenType: SLP_TOKEN_TYPE_FUNGIBLE, | ||||
| }, | }, | ||||
| ] as payment.TokenAction[], | ] as payment.TokenAction[], | ||||
| ▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | context('finalizeOutputs', () => { | ||||
| ], | ], | ||||
| DUMMY_CHANGE_SCRIPT, | DUMMY_CHANGE_SCRIPT, | ||||
| ), | ), | ||||
| ).to.throw( | ).to.throw( | ||||
| Error, | Error, | ||||
| `ecash-wallet does not support minting and sending the same token in the same Action. tokenActions MINT and SEND ${tokenIdThisAction}.`, | `ecash-wallet does not support minting and sending the same token in the same Action. tokenActions MINT and SEND ${tokenIdThisAction}.`, | ||||
| ); | ); | ||||
| }); | }); | ||||
| it('Throws if action exceeds SLP_TOKEN_TYPE_FUNGIBLE max outputs per tx', () => { | it('Throws if action exceeds SLP_MAX_SEND_OUTPUTS max outputs per tx', () => { | ||||
| const tokenIdThisAction = `11`.repeat(32); | const tokenIdThisAction = `11`.repeat(32); | ||||
| const atomsPerOutput = 1_000n; | const atomsPerOutput = 1_000n; | ||||
| const tooManySendOutputs = Array(SLP_MAX_SEND_OUTPUTS + 1).fill( | const tooManySendOutputs = Array(SLP_MAX_SEND_OUTPUTS + 1).fill( | ||||
| { | { | ||||
| sats: 546n, | sats: 546n, | ||||
| tokenId: tokenIdThisAction, | tokenId: tokenIdThisAction, | ||||
| atoms: atomsPerOutput, | atoms: atomsPerOutput, | ||||
| script: DUMMY_SCRIPT, | script: DUMMY_SCRIPT, | ||||
| ▲ Show 20 Lines • Show All 193 Lines • ▼ Show 20 Lines | context('finalizeOutputs', () => { | ||||
| tokenId: | tokenId: | ||||
| payment.GENESIS_TOKEN_ID_PLACEHOLDER, | payment.GENESIS_TOKEN_ID_PLACEHOLDER, | ||||
| isMintBaton: true, | isMintBaton: true, | ||||
| }, | }, | ||||
| ], | ], | ||||
| tokenActions: [ | tokenActions: [ | ||||
| { | { | ||||
| type: 'GENESIS', | type: 'GENESIS', | ||||
| tokenType: | tokenType: SLP_TOKEN_TYPE_FUNGIBLE, | ||||
| TOKEN_TYPE_SLP_TOKEN_TYPE_FUNGIBLE, | |||||
| genesisInfo: nullGenesisInfo, | genesisInfo: nullGenesisInfo, | ||||
| }, | }, | ||||
| ] as payment.TokenAction[], | ] as payment.TokenAction[], | ||||
| }, | }, | ||||
| [DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_FUNGIBLE], | [DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_FUNGIBLE], | ||||
| DUMMY_CHANGE_SCRIPT, | DUMMY_CHANGE_SCRIPT, | ||||
| ), | ), | ||||
| ).to.throw( | ).to.throw( | ||||
| ▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | context('finalizeOutputs', () => { | ||||
| tokenId: | tokenId: | ||||
| payment.GENESIS_TOKEN_ID_PLACEHOLDER, | payment.GENESIS_TOKEN_ID_PLACEHOLDER, | ||||
| isMintBaton: true, | isMintBaton: true, | ||||
| }, | }, | ||||
| ], | ], | ||||
| tokenActions: [ | tokenActions: [ | ||||
| { | { | ||||
| type: 'GENESIS', | type: 'GENESIS', | ||||
| tokenType: | tokenType: SLP_TOKEN_TYPE_FUNGIBLE, | ||||
| TOKEN_TYPE_SLP_TOKEN_TYPE_FUNGIBLE, | |||||
| genesisInfo: nullGenesisInfo, | genesisInfo: nullGenesisInfo, | ||||
| }, | }, | ||||
| ] as payment.TokenAction[], | ] as payment.TokenAction[], | ||||
| }, | }, | ||||
| [DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_FUNGIBLE], | [DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_FUNGIBLE], | ||||
| DUMMY_CHANGE_SCRIPT, | DUMMY_CHANGE_SCRIPT, | ||||
| ), | ), | ||||
| ).to.throw( | ).to.throw( | ||||
| Show All 33 Lines | context('finalizeOutputs', () => { | ||||
| tokenId: | tokenId: | ||||
| payment.GENESIS_TOKEN_ID_PLACEHOLDER, | payment.GENESIS_TOKEN_ID_PLACEHOLDER, | ||||
| isMintBaton: false, | isMintBaton: false, | ||||
| }, | }, | ||||
| ], | ], | ||||
| tokenActions: [ | tokenActions: [ | ||||
| { | { | ||||
| type: 'GENESIS', | type: 'GENESIS', | ||||
| tokenType: | tokenType: SLP_TOKEN_TYPE_FUNGIBLE, | ||||
| TOKEN_TYPE_SLP_TOKEN_TYPE_FUNGIBLE, | |||||
| genesisInfo: nullGenesisInfo, | genesisInfo: nullGenesisInfo, | ||||
| }, | }, | ||||
| ] as payment.TokenAction[], | ] as payment.TokenAction[], | ||||
| }, | }, | ||||
| [DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_FUNGIBLE], | [DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_FUNGIBLE], | ||||
| DUMMY_CHANGE_SCRIPT, | DUMMY_CHANGE_SCRIPT, | ||||
| ), | ), | ||||
| ).to.throw( | ).to.throw( | ||||
| ▲ Show 20 Lines • Show All 109 Lines • ▼ Show 20 Lines | |||||
| tokenId: | tokenId: | ||||
| payment.GENESIS_TOKEN_ID_PLACEHOLDER, | payment.GENESIS_TOKEN_ID_PLACEHOLDER, | ||||
| isMintBaton: true, | isMintBaton: true, | ||||
| }, | }, | ||||
| ], | ], | ||||
| tokenActions: [ | tokenActions: [ | ||||
| { | { | ||||
| type: 'GENESIS', | type: 'GENESIS', | ||||
| tokenType: | tokenType: SLP_TOKEN_TYPE_FUNGIBLE, | ||||
| TOKEN_TYPE_SLP_TOKEN_TYPE_FUNGIBLE, | |||||
| genesisInfo: nullGenesisInfo, | genesisInfo: nullGenesisInfo, | ||||
| }, | }, | ||||
| ] as payment.TokenAction[], | ] as payment.TokenAction[], | ||||
| }, | }, | ||||
| [DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_FUNGIBLE], | [DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_FUNGIBLE], | ||||
| DUMMY_CHANGE_SCRIPT, | DUMMY_CHANGE_SCRIPT, | ||||
| ), | ), | ||||
| ).to.throw( | ).to.throw( | ||||
| ▲ Show 20 Lines • Show All 219 Lines • ▼ Show 20 Lines | |||||
| tokenId: | tokenId: | ||||
| payment.GENESIS_TOKEN_ID_PLACEHOLDER, | payment.GENESIS_TOKEN_ID_PLACEHOLDER, | ||||
| isMintBaton: true, | isMintBaton: true, | ||||
| }, | }, | ||||
| ], | ], | ||||
| tokenActions: [ | tokenActions: [ | ||||
| { | { | ||||
| type: 'GENESIS', | type: 'GENESIS', | ||||
| tokenType: | tokenType: ALP_TOKEN_TYPE_STANDARD, | ||||
| TOKEN_TYPE_ALP_TOKEN_TYPE_STANDARD, | |||||
| genesisInfo: nullGenesisInfo, | genesisInfo: nullGenesisInfo, | ||||
| }, | }, | ||||
| ] as payment.TokenAction[], | ] as payment.TokenAction[], | ||||
| }, | }, | ||||
| [DUMMY_TOKEN_UTXO_ALP_TOKEN_TYPE_STANDARD], | [DUMMY_TOKEN_UTXO_ALP_TOKEN_TYPE_STANDARD], | ||||
| DUMMY_CHANGE_SCRIPT, | DUMMY_CHANGE_SCRIPT, | ||||
| ), | ), | ||||
| ).to.throw( | ).to.throw( | ||||
| ▲ Show 20 Lines • Show All 65 Lines • ▼ Show 20 Lines | |||||
| tokenId: | tokenId: | ||||
| payment.GENESIS_TOKEN_ID_PLACEHOLDER, | payment.GENESIS_TOKEN_ID_PLACEHOLDER, | ||||
| isMintBaton: false, | isMintBaton: false, | ||||
| }, | }, | ||||
| ], | ], | ||||
| tokenActions: [ | tokenActions: [ | ||||
| { | { | ||||
| type: 'GENESIS', | type: 'GENESIS', | ||||
| tokenType: | tokenType: ALP_TOKEN_TYPE_STANDARD, | ||||
| TOKEN_TYPE_ALP_TOKEN_TYPE_STANDARD, | |||||
| genesisInfo: nullGenesisInfo, | genesisInfo: nullGenesisInfo, | ||||
| }, | }, | ||||
| ] as payment.TokenAction[], | ] as payment.TokenAction[], | ||||
| }, | }, | ||||
| [DUMMY_TOKEN_UTXO_ALP_TOKEN_TYPE_STANDARD], | [DUMMY_TOKEN_UTXO_ALP_TOKEN_TYPE_STANDARD], | ||||
| DUMMY_CHANGE_SCRIPT, | DUMMY_CHANGE_SCRIPT, | ||||
| ), | ), | ||||
| ).to.throw( | ).to.throw( | ||||
| ▲ Show 20 Lines • Show All 535 Lines • ▼ Show 20 Lines | |||||
| `50` + | `50` + | ||||
| burnTwoEmpp + | burnTwoEmpp + | ||||
| sendOneEmpp + | sendOneEmpp + | ||||
| burnOneEmpp + | burnOneEmpp + | ||||
| sendTwoEmpp, | sendTwoEmpp, | ||||
| ); | ); | ||||
| }); | }); | ||||
| }); | }); | ||||
| context('SLP_TOKEN_TYPE_MINT_VAULT', () => { | |||||
| it('Throws if action is associated with more than one tokenId', () => { | |||||
| expect(() => | |||||
| finalizeOutputs( | |||||
| { | |||||
| outputs: [ | |||||
| { sats: 0n }, | |||||
| { | |||||
| sats: 546n, | |||||
| tokenId: '11'.repeat(32), | |||||
| atoms: 1_000_000n, | |||||
| script: DUMMY_SCRIPT, | |||||
| isMintBaton: false, | |||||
| }, | |||||
| { | |||||
| sats: 546n, | |||||
| tokenId: '22'.repeat(32), | |||||
| atoms: 1_000_000n, | |||||
| script: DUMMY_SCRIPT, | |||||
| isMintBaton: false, | |||||
| }, | |||||
| ], | |||||
| tokenActions: [ | |||||
| { | |||||
| type: 'SEND', | |||||
| tokenId: '11'.repeat(32), | |||||
| tokenType: ALP_TOKEN_TYPE_STANDARD, | |||||
| }, | |||||
| { | |||||
| type: 'SEND', | |||||
| tokenId: '22'.repeat(32), | |||||
| tokenType: SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| }, | |||||
| ] as payment.TokenAction[], | |||||
| }, | |||||
| [ | |||||
| { | |||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| token: { | |||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT.token, | |||||
| tokenId: '11'.repeat(32), | |||||
| atoms: 1_000_000_000n, | |||||
| } as Token, | |||||
| }, | |||||
| { | |||||
| ...DUMMY_TOKEN_UTXO_ALP_TOKEN_TYPE_STANDARD, | |||||
| token: { | |||||
| ...DUMMY_TOKEN_UTXO_ALP_TOKEN_TYPE_STANDARD.token, | |||||
| tokenId: '22'.repeat(32), | |||||
| atoms: 1_000_000_000n, | |||||
| } as Token, | |||||
| }, | |||||
| ], | |||||
| DUMMY_CHANGE_SCRIPT, | |||||
| ), | |||||
| ).to.throw( | |||||
| Error, | |||||
| 'Action must include only one token type. Found (at least) two: ALP_TOKEN_TYPE_STANDARD and SLP_TOKEN_TYPE_MINT_VAULT.', | |||||
| ); | |||||
| }); | |||||
| it('Throws if we have a genesisAction and another token action', () => { | |||||
| expect(() => | |||||
| finalizeOutputs( | |||||
| { | |||||
| outputs: [ | |||||
| // Blank OP_RETURN | |||||
| { sats: 0n }, | |||||
| // Genesis mint qty at correct outIdx for SLP_TOKEN_TYPE_MINT_VAULT | |||||
| { | |||||
| sats: 546n, | |||||
| atoms: 1_000_000n, | |||||
| script: DUMMY_SCRIPT, | |||||
| tokenId: | |||||
| payment.GENESIS_TOKEN_ID_PLACEHOLDER, | |||||
| isMintBaton: false, | |||||
| }, | |||||
| // Token SEND output unrelated to genesis | |||||
| { | |||||
| sats: 546n, | |||||
| tokenId: '11'.repeat(32), | |||||
| atoms: 1_000_000n, | |||||
| script: DUMMY_SCRIPT, | |||||
| isMintBaton: false, | |||||
| }, | |||||
| ], | |||||
| tokenActions: [ | |||||
| { | |||||
| type: 'GENESIS', | |||||
| tokenType: SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| genesisInfo: nullGenesisInfo, | |||||
| }, | |||||
| { | |||||
| type: 'SEND', | |||||
| tokenId: '11'.repeat(32), | |||||
| tokenType: SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| }, | |||||
| ] as payment.TokenAction[], | |||||
| }, | |||||
| [ | |||||
| { | |||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| token: { | |||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT.token, | |||||
| tokenId: '11'.repeat(32), | |||||
| atoms: 1_000_000_000n, | |||||
| } as Token, | |||||
| }, | |||||
| ], | |||||
| DUMMY_CHANGE_SCRIPT, | |||||
| ), | |||||
| ).to.throw( | |||||
| Error, | |||||
| 'SLP_TOKEN_TYPE_MINT_VAULT token txs may only have a single token action. 2 tokenActions specified.', | |||||
| ); | |||||
| }); | |||||
| it('Throws if action combines MINT and SEND outputs', () => { | |||||
| const tokenIdThisAction = `11`.repeat(32); | |||||
| // Note we throw here because we don't support minting SLP_TOKEN_TYPE_MINT_VAULT tokens at all | |||||
| // But, when we do, we would still expect to throw here | |||||
| expect(() => | |||||
| finalizeOutputs( | |||||
| { | |||||
| outputs: [ | |||||
| { sats: 0n }, | |||||
| // Send output | |||||
| { | |||||
| sats: 546n, | |||||
| tokenId: tokenIdThisAction, | |||||
| atoms: 1_000_000n, | |||||
| script: DUMMY_SCRIPT, | |||||
| isMintBaton: false, | |||||
| }, | |||||
| // Mint output | |||||
| { | |||||
| sats: 546n, | |||||
| tokenId: tokenIdThisAction, | |||||
| atoms: 1_000_000n, | |||||
| script: DUMMY_SCRIPT, | |||||
| isMintBaton: false, | |||||
| }, | |||||
| ], | |||||
| tokenActions: [ | |||||
| { | |||||
| type: 'SEND', | |||||
| tokenId: tokenIdThisAction, | |||||
| tokenType: SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| }, | |||||
| { | |||||
| type: 'MINT', | |||||
| tokenId: tokenIdThisAction, | |||||
| tokenType: SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| }, | |||||
| ] as payment.TokenAction[], | |||||
| }, | |||||
| [ | |||||
| { | |||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| token: { | |||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT.token, | |||||
| tokenId: tokenIdThisAction, | |||||
| atoms: 1_000_000_000n, | |||||
| } as Token, | |||||
| }, | |||||
| ], | |||||
| DUMMY_CHANGE_SCRIPT, | |||||
| ), | |||||
| ).to.throw( | |||||
| Error, | |||||
| `ecash-wallet does not currently support minting SLP_TOKEN_TYPE_MINT_VAULT tokens.`, | |||||
| ); | |||||
| }); | |||||
| it('Throws if action exceeds SLP_MAX_SEND_OUTPUTS max outputs per tx', () => { | |||||
| const tokenIdThisAction = `11`.repeat(32); | |||||
| const atomsPerOutput = 1_000n; | |||||
| const tooManySendOutputs = Array(SLP_MAX_SEND_OUTPUTS + 1).fill( | |||||
| { | |||||
| sats: 546n, | |||||
| tokenId: tokenIdThisAction, | |||||
| atoms: atomsPerOutput, | |||||
| script: DUMMY_SCRIPT, | |||||
| isMintBaton: false, | |||||
| }, | |||||
| ); | |||||
| expect(() => | |||||
| finalizeOutputs( | |||||
| { | |||||
| outputs: [{ sats: 0n }, ...tooManySendOutputs], | |||||
| tokenActions: [ | |||||
| { | |||||
| type: 'SEND', | |||||
| tokenId: tokenIdThisAction, | |||||
| tokenType: SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| }, | |||||
| ] as payment.TokenAction[], | |||||
| }, | |||||
| [ | |||||
| { | |||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| token: { | |||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT.token, | |||||
| tokenId: tokenIdThisAction, | |||||
| /** Exactly specify outputs so no change output is added */ | |||||
| atoms: | |||||
| BigInt(SLP_MAX_SEND_OUTPUTS + 1) * | |||||
| atomsPerOutput, | |||||
| } as Token, | |||||
| }, | |||||
| ], | |||||
| DUMMY_CHANGE_SCRIPT, | |||||
| ), | |||||
| ).to.throw( | |||||
| Error, | |||||
| `An SLP SLP_TOKEN_TYPE_MINT_VAULT Action may not have more than ${SLP_MAX_SEND_OUTPUTS} token outputs, and no outputs may be at outIdx > ${SLP_MAX_SEND_OUTPUTS}. Found output at outIdx 20.`, | |||||
| ); | |||||
| }); | |||||
| it('Throws if generating a token change output will cause us to exceed SLP max outputs per tx', () => { | |||||
| const tokenIdThisAction = `11`.repeat(32); | |||||
| // Fill it up with "just enough" outputs and no change expected | |||||
| const tooManySendOutputs = Array(SLP_MAX_SEND_OUTPUTS).fill({ | |||||
| sats: 546n, | |||||
| tokenId: tokenIdThisAction, | |||||
| atoms: 1_000_000n, | |||||
| script: DUMMY_SCRIPT, | |||||
| isMintBaton: false, | |||||
| }); | |||||
| expect(() => | |||||
| finalizeOutputs( | |||||
| { | |||||
| outputs: [{ sats: 0n }, ...tooManySendOutputs], | |||||
| tokenActions: [ | |||||
| { | |||||
| type: 'SEND', | |||||
| tokenId: tokenIdThisAction, | |||||
| tokenType: SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| }, | |||||
| ] as payment.TokenAction[], | |||||
| }, | |||||
| [ | |||||
| { | |||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| token: { | |||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT.token, | |||||
| tokenId: tokenIdThisAction, | |||||
| atoms: 1_000_000_000n, | |||||
| } as Token, | |||||
| }, | |||||
| ], | |||||
| DUMMY_CHANGE_SCRIPT, | |||||
| ), | |||||
| ).to.throw( | |||||
| Error, | |||||
| `Tx needs a token change output to avoid burning atoms of ${tokenIdThisAction}, but the token change output would be at outIdx 20 which is greater than the maximum allowed outIdx of ${SLP_MAX_SEND_OUTPUTS} for SLP_TOKEN_TYPE_MINT_VAULT.`, | |||||
| ); | |||||
| }); | |||||
| it('DOES NOT throw if output atoms exactly match input atoms at max outputs, so no change output is generated', () => { | |||||
| const tokenIdThisAction = `11`.repeat(32); | |||||
| // Fill it up with "just enough" outputs but requiredUtxos expect change | |||||
| const tooManySendOutputs = Array(SLP_MAX_SEND_OUTPUTS).fill({ | |||||
| sats: 546n, | |||||
| tokenId: tokenIdThisAction, | |||||
| atoms: 1_000_000n, | |||||
| script: DUMMY_SCRIPT, | |||||
| isMintBaton: false, | |||||
| }); | |||||
| const testAction = { | |||||
| outputs: [{ sats: 0n }, ...tooManySendOutputs], | |||||
| tokenActions: [ | |||||
| { | |||||
| type: 'SEND', | |||||
| tokenId: tokenIdThisAction, | |||||
| tokenType: SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| }, | |||||
| ] as payment.TokenAction[], | |||||
| }; | |||||
| const result = finalizeOutputs( | |||||
| testAction, | |||||
| [ | |||||
| { | |||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| token: { | |||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT.token, | |||||
| tokenId: tokenIdThisAction, | |||||
| atoms: | |||||
| BigInt(SLP_MAX_SEND_OUTPUTS) * 1_000_000n, | |||||
| } as Token, | |||||
| }, | |||||
| ], | |||||
| DUMMY_CHANGE_SCRIPT, | |||||
| ); | |||||
| // No error thrown, returns processed outputs | |||||
| expect(result).to.have.length(SLP_MAX_SEND_OUTPUTS + 1); | |||||
| // Original action remains unchanged | |||||
| expect(testAction.outputs.length).to.equal( | |||||
| SLP_MAX_SEND_OUTPUTS + 1, | |||||
| ); | |||||
| }); | |||||
| it('DOES NOT throw and adds change if adding change does not push us over the output limit', () => { | |||||
| const tokenIdThisAction = `11`.repeat(32); | |||||
| // Fill it with enough outputs so that 1 change output puts us at the max | |||||
| const tooManySendOutputs = Array(SLP_MAX_SEND_OUTPUTS - 1).fill( | |||||
| { | |||||
| sats: 546n, | |||||
| tokenId: tokenIdThisAction, | |||||
| atoms: 1_000_000n, | |||||
| script: DUMMY_SCRIPT, | |||||
| isMintBaton: false, | |||||
| }, | |||||
| ); | |||||
| const testAction = { | |||||
| outputs: [{ sats: 0n }, ...tooManySendOutputs], | |||||
| tokenActions: [ | |||||
| { | |||||
| type: 'SEND', | |||||
| tokenId: tokenIdThisAction, | |||||
| tokenType: SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| }, | |||||
| ] as payment.TokenAction[], | |||||
| }; | |||||
| const outputsLengthBeforeChange = testAction.outputs.length; | |||||
| expect(outputsLengthBeforeChange).to.equal(19); | |||||
| const expectedChangeAtoms = 55n; | |||||
| const result = finalizeOutputs( | |||||
| testAction, | |||||
| [ | |||||
| { | |||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| token: { | |||||
| ...DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT.token, | |||||
| tokenId: tokenIdThisAction, | |||||
| atoms: | |||||
| BigInt(SLP_MAX_SEND_OUTPUTS - 1) * | |||||
| 1_000_000n + | |||||
| expectedChangeAtoms, | |||||
| } as Token, | |||||
| }, | |||||
| ], | |||||
| DUMMY_CHANGE_SCRIPT, | |||||
| ); | |||||
| // No error thrown, check returned outputs | |||||
| expect(result).to.have.length(outputsLengthBeforeChange + 1); | |||||
| // The OP_RETURN has been written in the returned results | |||||
| const opReturn = result[0].script.toHex(); | |||||
| expect(opReturn).to.equal( | |||||
| `6a04534c500001020453454e442011111111111111111111111111111111111111111111111111111111111111110800000000000f42400800000000000f42400800000000000f42400800000000000f42400800000000000f42400800000000000f42400800000000000f42400800000000000f42400800000000000f42400800000000000f42400800000000000f42400800000000000f42400800000000000f42400800000000000f42400800000000000f42400800000000000f42400800000000000f42400800000000000f4240080000000000000037`, | |||||
| ); | |||||
| // The last token output is the change output; it has expected atoms | |||||
| expect(BigInt(parseInt(opReturn.slice(-2), 16))).to.equal( | |||||
| expectedChangeAtoms, | |||||
| ); | |||||
| }); | |||||
| it('Throws if a genesisAction includes any mint batons', () => { | |||||
| expect(() => | |||||
| finalizeOutputs( | |||||
| { | |||||
| outputs: [ | |||||
| { sats: 0n }, | |||||
| // Mint qty output at outIdx 1, per spec | |||||
| { | |||||
| sats: 546n, | |||||
| script: DUMMY_SCRIPT, | |||||
| atoms: 10n, | |||||
| tokenId: | |||||
| payment.GENESIS_TOKEN_ID_PLACEHOLDER, | |||||
| isMintBaton: false, | |||||
| }, | |||||
| // Baton is at outIdx 2 | |||||
| { | |||||
| sats: 546n, | |||||
| script: DUMMY_SCRIPT, | |||||
| atoms: 0n, | |||||
| tokenId: | |||||
| payment.GENESIS_TOKEN_ID_PLACEHOLDER, | |||||
| isMintBaton: true, | |||||
| }, | |||||
| ], | |||||
| tokenActions: [ | |||||
| { | |||||
| type: 'GENESIS', | |||||
| tokenType: SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| genesisInfo: nullGenesisInfo, | |||||
| }, | |||||
| ] as payment.TokenAction[], | |||||
| }, | |||||
| [DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT], | |||||
| DUMMY_CHANGE_SCRIPT, | |||||
| ), | |||||
| ).to.throw( | |||||
| Error, | |||||
| 'An SLP SLP_TOKEN_TYPE_MINT_VAULT Action may not have any mint batons.', | |||||
| ); | |||||
| }); | |||||
| it('Throws if genesisAction does not include mint qty at outIdx 1', () => { | |||||
| expect(() => | |||||
| finalizeOutputs( | |||||
| { | |||||
| outputs: [ | |||||
| { sats: 0n }, | |||||
| // Sats at outIdx 1 | |||||
| { | |||||
| sats: 10000n, | |||||
| script: DUMMY_SCRIPT, | |||||
| }, | |||||
| // Mint qty at outIdx 2 | |||||
| { | |||||
| sats: 546n, | |||||
| script: DUMMY_SCRIPT, | |||||
| atoms: 10n, | |||||
| tokenId: | |||||
| payment.GENESIS_TOKEN_ID_PLACEHOLDER, | |||||
| isMintBaton: false, | |||||
| }, | |||||
| ], | |||||
| tokenActions: [ | |||||
| { | |||||
| type: 'GENESIS', | |||||
| tokenType: SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| genesisInfo: nullGenesisInfo, | |||||
| }, | |||||
| ] as payment.TokenAction[], | |||||
| }, | |||||
| [DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT], | |||||
| DUMMY_CHANGE_SCRIPT, | |||||
| ), | |||||
| ).to.throw( | |||||
| Error, | |||||
| 'Genesis action for SLP_TOKEN_TYPE_MINT_VAULT token specified, but no mint quantity output found at outIdx 1. This is a spec requirement for SLP SLP_TOKEN_TYPE_MINT_VAULT tokens.', | |||||
| ); | |||||
| }); | |||||
| it('Throws on MINT action (not currently supported)', () => { | |||||
| expect(() => | |||||
| finalizeOutputs( | |||||
| { | |||||
| outputs: [ | |||||
| { sats: 0n }, | |||||
| // Mint qty | |||||
| { | |||||
| sats: 546n, | |||||
| script: DUMMY_SCRIPT, | |||||
| tokenId: '11'.repeat(32), | |||||
| atoms: 10n, | |||||
| isMintBaton: false, | |||||
| }, | |||||
| ], | |||||
| tokenActions: [ | |||||
| { | |||||
| type: 'MINT', | |||||
| tokenId: '11'.repeat(32), | |||||
| tokenType: SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| }, | |||||
| ] as payment.TokenAction[], | |||||
| }, | |||||
| [DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT], | |||||
| DUMMY_CHANGE_SCRIPT, | |||||
| ), | |||||
| ).to.throw( | |||||
| Error, | |||||
| `ecash-wallet does not currently support minting SLP_TOKEN_TYPE_MINT_VAULT tokens.`, | |||||
| ); | |||||
| }); | |||||
| it('Throws if genesisAction includes more than one mint qty output', () => { | |||||
| expect(() => | |||||
| finalizeOutputs( | |||||
| { | |||||
| outputs: [ | |||||
| { sats: 0n }, | |||||
| // Mint qty output at outIdx 1, per spec | |||||
| { | |||||
| sats: 546n, | |||||
| script: DUMMY_SCRIPT, | |||||
| atoms: 10n, | |||||
| tokenId: | |||||
| payment.GENESIS_TOKEN_ID_PLACEHOLDER, | |||||
| isMintBaton: false, | |||||
| }, | |||||
| // Another mint qty output | |||||
| { | |||||
| sats: 546n, | |||||
| script: DUMMY_SCRIPT, | |||||
| atoms: 10n, | |||||
| tokenId: | |||||
| payment.GENESIS_TOKEN_ID_PLACEHOLDER, | |||||
| isMintBaton: false, | |||||
| }, | |||||
| ], | |||||
| tokenActions: [ | |||||
| { | |||||
| type: 'GENESIS', | |||||
| tokenType: SLP_TOKEN_TYPE_MINT_VAULT, | |||||
| genesisInfo: nullGenesisInfo, | |||||
| }, | |||||
| ] as payment.TokenAction[], | |||||
| }, | |||||
| [DUMMY_TOKEN_UTXO_SLP_TOKEN_TYPE_MINT_VAULT], | |||||
| DUMMY_CHANGE_SCRIPT, | |||||
| ), | |||||
| ).to.throw( | |||||
| Error, | |||||
| `An SLP SLP_TOKEN_TYPE_MINT_VAULT GENESIS tx may have only one mint qty output and it must be at outIdx 1. Found another mint qty output at outIdx 2.`, | |||||
| ); | |||||
| }); | |||||
| }); | |||||
| }); | }); | ||||
| }); | }); | ||||
| describe('static methods', () => { | describe('static methods', () => { | ||||
| context('Can sum value of utxos', () => { | context('Can sum value of utxos', () => { | ||||
| it('Returns 0 if called with no utxos', () => { | it('Returns 0 if called with no utxos', () => { | ||||
| expect(Wallet.sumUtxosSats([])).to.equal(0n); | expect(Wallet.sumUtxosSats([])).to.equal(0n); | ||||
| }); | }); | ||||
| it('Can get total eCash satoshis held by all kinds of utxos', () => { | it('Can get total eCash satoshis held by all kinds of utxos', () => { | ||||
| expect(Wallet.sumUtxosSats(ALL_SUPPORTED_UTXOS)).to.equal( | expect(Wallet.sumUtxosSats(ALL_SUPPORTED_UTXOS)).to.equal( | ||||
| 93751638n, | 93753276n, | ||||
| ); | ); | ||||
| }); | }); | ||||
| }); | }); | ||||
| }); | }); | ||||