diff --git a/modules/chronik-client/README.md b/modules/chronik-client/README.md --- a/modules/chronik-client/README.md +++ b/modules/chronik-client/README.md @@ -99,3 +99,4 @@ 0.21.0 - Skipped as accidentally published 0.22.0 before diff approval at 0.21.1-rc.1 0.22.0 - Add support for `tokenId` endpoints and token data in utxos to `ChronikClientNode` 0.22.1 - Return `script` key for utxos fetched from `tokenId` endpoint +0.23.0 - Add support for returning `TokenInfo` from `chronik.token(tokenId)` calls to `ChronikClientNode` diff --git a/modules/chronik-client/package-lock.json b/modules/chronik-client/package-lock.json --- a/modules/chronik-client/package-lock.json +++ b/modules/chronik-client/package-lock.json @@ -1,12 +1,12 @@ { "name": "chronik-client", - "version": "0.22.1", + "version": "0.23.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "chronik-client", - "version": "0.22.1", + "version": "0.23.0", "license": "MIT", "dependencies": { "@types/ws": "^8.2.1", diff --git a/modules/chronik-client/package.json b/modules/chronik-client/package.json --- a/modules/chronik-client/package.json +++ b/modules/chronik-client/package.json @@ -1,6 +1,6 @@ { "name": "chronik-client", - "version": "0.22.1", + "version": "0.23.0", "description": "A client for accessing the Chronik Indexer API", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/modules/chronik-client/src/ChronikClientNode.ts b/modules/chronik-client/src/ChronikClientNode.ts --- a/modules/chronik-client/src/ChronikClientNode.ts +++ b/modules/chronik-client/src/ChronikClientNode.ts @@ -129,6 +129,13 @@ return blocks.blocks.map(convertToBlockInfo); } + /** Fetch token info and stats given the tokenId. */ + public async token(tokenId: string): Promise { + const data = await this._proxyInterface.get(`/token/${tokenId}`); + const tokenInfo = proto.TokenInfo.decode(data); + return convertToTokenInfo(tokenInfo); + } + /** Fetch tx details given the txid. */ public async tx(txid: string): Promise { const data = await this._proxyInterface.get(`/tx/${txid}`); @@ -863,6 +870,71 @@ return 'UNRECOGNIZED'; } +function convertToTokenInfo(tokenInfo: proto.TokenInfo): TokenInfo { + if (typeof tokenInfo.tokenType === 'undefined') { + // Not expected to ever happen + throw new Error( + `chronik returned undefined tokenInfo.tokenType for tokenId "${tokenInfo.tokenId}"`, + ); + } + if (typeof tokenInfo.genesisInfo === 'undefined') { + // Not expected to ever happen + throw new Error( + `chronik returned undefined tokenInfo.genesisInfo for tokenId "${tokenInfo.tokenId}"`, + ); + } + + // We use tokenType to get the correct shape of genesisInfo + const tokenType = convertToTokenType(tokenInfo.tokenType); + + const returnedTokenInfo: TokenInfo = { + tokenId: tokenInfo.tokenId, + tokenType, + timeFirstSeen: tokenInfo.timeFirstSeen, + genesisInfo: convertToGenesisInfo(tokenInfo.genesisInfo, tokenType), + }; + + // Only include block if the tx is confirmed + if (typeof tokenInfo.block !== 'undefined') { + returnedTokenInfo.block = convertToBlockMeta(tokenInfo.block); + } + + return returnedTokenInfo; +} + +function convertToGenesisInfo( + genesisInfo: proto.GenesisInfo, + tokenType: TokenType, +): GenesisInfo { + const decoder = new TextDecoder(); + const returnedGenesisInfo: GenesisInfo = { + tokenTicker: decoder.decode(genesisInfo.tokenTicker), + tokenName: decoder.decode(genesisInfo.tokenName), + url: decoder.decode(genesisInfo.url), + decimals: genesisInfo.decimals, + }; + + // Add ALP fields for ALP types only + if (tokenType.protocol === 'ALP') { + returnedGenesisInfo.data = genesisInfo.data; + returnedGenesisInfo.authPubkey = toHex(genesisInfo.authPubkey); + } + + // Add mintVaultHash for SLP Mint Vault only + if (tokenType.type === 'SLP_TOKEN_TYPE_MINT_VAULT') { + returnedGenesisInfo.mintVaultScripthash = toHex( + genesisInfo.mintVaultScripthash, + ); + } + + // Add url for SLP only + if (tokenType.protocol === 'SLP') { + returnedGenesisInfo.hash = toHex(genesisInfo.hash); + } + + return returnedGenesisInfo; +} + function isTxMsgType(msgType: any): msgType is TxMsgType { return TX_MSG_TYPES.includes(msgType); } @@ -1322,3 +1394,41 @@ /** UTXOs */ utxos: Utxo_InNode[]; } + +/** Info about a token */ +export interface TokenInfo { + /** + * Hex token_id (in big-endian, like usually displayed to users) of the token. + * This is not `bytes` because SLP and ALP use different endiannnes, + * so to avoid this we use hex, which conventionally implies big-endian in a bitcoin context. + */ + tokenId: string; + /** Token type of the token */ + tokenType: TokenType; + /** Info found in the token's GENESIS tx */ + genesisInfo: GenesisInfo; + /** Block of the GENESIS tx, if it's mined already */ + block?: BlockMetadata_InNode; + /** Time the GENESIS tx has first been seen by the indexer */ + timeFirstSeen: string; +} + +/** Genesis info found in GENESIS txs of tokens */ +export interface GenesisInfo { + /** token_ticker of the token */ + tokenTicker: string; + /** token_name of the token */ + tokenName: string; + /** URL of the token */ + url: string; + /** token_document_hash of the token (only on SLP) */ + hash?: string; + /** mint_vault_scripthash (only on SLP V2 Mint Vault) */ + mintVaultScripthash?: string; + /** Arbitray payload data of the token (only on ALP) */ + data?: Uint8Array; + /** auth_pubkey of the token (only on ALP) */ + authPubkey?: string; + /** decimals of the token, i.e. how many decimal places the token should be displayed with. */ + decimals: number; +} diff --git a/modules/chronik-client/test/integration/token_alp.ts b/modules/chronik-client/test/integration/token_alp.ts --- a/modules/chronik-client/test/integration/token_alp.ts +++ b/modules/chronik-client/test/integration/token_alp.ts @@ -8,6 +8,7 @@ import { EventEmitter, once } from 'node:events'; import { ChronikClientNode, + TokenInfo, Token_InNode, TxHistoryPage_InNode, Tx_InNode, @@ -27,7 +28,7 @@ let get_alp_multi_txid: Promise; let get_alp_mega_txid: Promise; let get_alp_mint_two_txid: Promise; - let get_alp_send_two_txid: Promise; + let get_alp_nonutf8_genesis_txid: Promise; const statusEvent = new EventEmitter(); before(async () => { @@ -82,9 +83,9 @@ }); } - if (message && message.alp_send_two_txid) { - get_alp_send_two_txid = new Promise(resolve => { - resolve(message.alp_send_two_txid); + if (message && message.alp_nonutf8_genesis_txid) { + get_alp_nonutf8_genesis_txid = new Promise(resolve => { + resolve(message.alp_nonutf8_genesis_txid); }); } @@ -146,7 +147,7 @@ let alpMultiTxid = ''; let alpMegaTxid = ''; let alpMintTwoTxid = ''; - let alpSendTwoTxid = ''; + let alpNonUtf8GenesisTxid = ''; let alpGenesis: Tx_InNode; let alpMint: Tx_InNode; @@ -157,6 +158,28 @@ let alpMintTwo: Tx_InNode; let alpSendTwo: Tx_InNode; + const alpTokenInfo: TokenInfo = { + tokenId: + '1111111111111111111111111111111111111111111111111111111111111111', + timeFirstSeen: '1300000000', + tokenType: { + protocol: 'ALP', + type: 'ALP_TOKEN_TYPE_STANDARD', + number: 0, + }, + // We do not get hash in GenesisInfo for ALP + // We get data and authPubkey keys in GenesisInfo for ALP + // We do not get mintVaultScripthash for non-SLP_MINT_VAULT + genesisInfo: { + tokenTicker: 'TEST', + tokenName: 'Test Token', + url: 'http://example.com', + data: new Uint8Array([84, 111, 107, 101, 110, 32, 68, 97, 116, 97]), + authPubkey: '546f6b656e205075626b6579', + decimals: 4, + }, + }; + let confirmedTxsForAlpGenesisTxid: TxHistoryPage_InNode; it('Gets an ALP genesis tx from the mempool', async () => { @@ -317,6 +340,27 @@ .history(); expect(historyForThisTokenId.txs.length).to.eql(1); expect(historyForThisTokenId.txs[0]).to.deep.equal(alpGenesis); + + // We can get token info of an alp token from the mempool + const alpGenesisMempoolInfo = await chronik.token(alpGenesisTxid); + expect(alpGenesisMempoolInfo).to.deep.equal({ + ...alpTokenInfo, + tokenId: alpGenesisTxid, + }); + // Invalid tokenId is rejected + await expect(chronik.token('somestring')).to.be.rejectedWith( + Error, + `Failed getting /token/somestring (): 400: Not a txid: somestring`, + ); + // We get expected error for a txid that is not in the mempool + await expect( + chronik.token( + '0dab1008db30343a4f771983e9fd96cbc15f0c6efc73f5249c9bae311ef1e92f', + ), + ).to.be.rejectedWith( + Error, + `Failed getting /token/0dab1008db30343a4f771983e9fd96cbc15f0c6efc73f5249c9bae311ef1e92f (): 404: Token 0dab1008db30343a4f771983e9fd96cbc15f0c6efc73f5249c9bae311ef1e92f not found in the index`, + ); }); it('Gets an ALP mint tx from the mempool', async () => { const chronikUrl = await chronik_url; @@ -393,6 +437,12 @@ // Normal status expect(alpMint.tokenStatus).to.eql('TOKEN_STATUS_NORMAL'); + + // Error is thrown for a txid that is in the mempool but is not a tokenId + await expect(chronik.token(alpMintTxid)).to.be.rejectedWith( + Error, + `Failed getting /token/0dab1008db30343a4f771983e9fd96cbc15f0c6efc73f5249c9bae311ef1e92f (): 404: Token 0dab1008db30343a4f771983e9fd96cbc15f0c6efc73f5249c9bae311ef1e92f not found in the index`, + ); }); it('Gets an ALP send tx from the mempool', async () => { const chronikUrl = await chronik_url; @@ -737,6 +787,18 @@ const chronikUrl = await chronik_url; const chronik = new ChronikClientNode(chronikUrl); + // Now that we have a block, we get a block key from token info + const alpGenesisConfirmedInfo = await chronik.token(alpGenesisTxid); + expect(alpGenesisConfirmedInfo).to.deep.equal({ + ...alpTokenInfo, + tokenId: alpGenesisTxid, + block: { + hash: '5e75fc2b2b101c4cf8beec2a68303fcdc5e6d0e3684cc8fbe5ebea60d781b1bb', + height: 102, + timestamp: 1300000500, + }, + }); + alpMegaTxid = await get_alp_mega_txid; // Can this one from the tx endpoint @@ -1112,15 +1174,31 @@ const chronikUrl = await chronik_url; const chronik = new ChronikClientNode(chronikUrl); + alpNonUtf8GenesisTxid = await get_alp_nonutf8_genesis_txid; alpMintTwoTxid = await get_alp_mint_two_txid; - alpSendTwoTxid = await get_alp_send_two_txid; // Can get the tx object from the tx endpoint alpMintTwo = await chronik.tx(alpMintTwoTxid); - alpSendTwo = await chronik.tx(alpSendTwoTxid); + alpSendTwo = await chronik.tx(alpNonUtf8GenesisTxid); + + // We can get genesis info even if utf8 expected fields are not utf8 + // In practice we do not expect this to ever happen, but someone could do this + // We confirm it doesn't break anything + const thisTokenInfo = await chronik.token(alpNonUtf8GenesisTxid); + + // hex 0304b60c048de8d9650881002834d6490000 + // A user of chronik-client would see output of '\x03\x04�\f\x04���e\b�\x00(4�I\x00\x00' for tokenInfo.genesisInfo.tokenName + expect(thisTokenInfo.genesisInfo?.tokenName).to.eql( + '\x03\x04\uFFFD\f\x04\uFFFD\uFFFD\uFFFDe\b\uFFFD\x00(4\uFFFDI\x00\x00', + ); + // hex 00fabe6d6d6f5486f62c703086014607f5bed91d093b092a8faf5ac882a0ccf462682a22f002 + // A user of chronik-client would see output of '\x00��mmoT��,p0�\x01F\x07���\x1D\t;\t*��ZȂ���bh*"�\x02' for thisTokenInfo.genesisInfo.url + expect(thisTokenInfo.genesisInfo?.url).to.eql( + '\x00\uFFFD\uFFFDmmoT\uFFFD\uFFFD,p0\uFFFD\x01F\x07\uFFFD\uFFFD\uFFFD\x1D\t;\t*\uFFFD\uFFFDZȂ\uFFFD\uFFFD\uFFFDbh*"\uFFFD\x02', + ); - // alpSendTwoTxid eb227bec6fd4d270262e14efa128a970f0ea4e529c599b1eb28af5fc8eaf3452 (broadcast 1st, alphabetically 2nd) - // alpMintTwoTxid 665c70b6c01b8e9b4865136ee650bae344fb8b45def49eb7ec303b04d2520b4b (broadcast 2nd, alphabetically 1st) + // alpNonUtf8GenesisTxid 65c63c950ec88a723cd37fc40f2e6f7732508a3703febed620b91d8b0c423eea (broadcast 1st, alphabetically 2nd) + // alpMintTwoTxid 163ebdbd2b915d090d602970b2e2737abf631a1ce31345c90d656e98b60e2b8c (broadcast 2nd, alphabetically 1st) const alphabeticalUnconfirmedAlpGenesisTxs = [ alpMintTwo, alpSendTwo, @@ -1283,8 +1361,8 @@ alpGenesisTxid, // blockheight 102, timeFirstSeen 1300000000 | bb4d71aa6c0a92144f854402f2677975ad86d3a72cb7b0fb48d02473a88fc6e2 alpMultiTxid, // blockheight 102, timeFirstSeen 1300000000 | e3c47f14d7ba3ab9a6f32a0fa8fcac41d06d3af595ebb5bab77ad03633a52eba alpSendTxid, //blockheight 102, timeFirstSeen 1300000000 | e623ab8971c93fa1a831a4310da65554c8dfd811c16cd5d41c6612268cb5dd5f - alpMintTwoTxid, // blockheight 103, timeFirstSeen 1300000000 | 665c70b6c01b8e9b4865136ee650bae344fb8b45def49eb7ec303b04d2520b4b - alpSendTwoTxid, // blockheight 103, timeFirstSeen 1300000000 | eb227bec6fd4d270262e14efa128a970f0ea4e529c599b1eb28af5fc8eaf3452 + alpMintTwoTxid, // blockheight 103, timeFirstSeen 1300000000 | 163ebdbd2b915d090d602970b2e2737abf631a1ce31345c90d656e98b60e2b8c + alpNonUtf8GenesisTxid, // blockheight 103, timeFirstSeen 1300000000 | 65c63c950ec88a723cd37fc40f2e6f7732508a3703febed620b91d8b0c423eea ]; // Same amount of txs in each @@ -1303,8 +1381,8 @@ // alpMegaTxid has timeFirstSeen of 0 because it had to be manually mined in block. So, it comes first in the next block // The other txids in the alpMegaTxid block all have the same timeFirstSeen, so they are sorted reverse alphabetically const historyAlpTxids = [ - alpSendTwoTxid, // timeFirstSeen 1300000000, blockheight 103 | eb227bec6fd4d270262e14efa128a970f0ea4e529c599b1eb28af5fc8eaf3452 - alpMintTwoTxid, // timeFirstSeen 1300000000, blockheight 103 | 665c70b6c01b8e9b4865136ee650bae344fb8b45def49eb7ec303b04d2520b4b + alpNonUtf8GenesisTxid, // timeFirstSeen 1300000000, blockheight 103 | 65c63c950ec88a723cd37fc40f2e6f7732508a3703febed620b91d8b0c423eea + alpMintTwoTxid, // timeFirstSeen 1300000000, blockheight 103 | 163ebdbd2b915d090d602970b2e2737abf631a1ce31345c90d656e98b60e2b8c alpMegaTxid, // timeFirstSeen 0, blockheight 102 | 72101f535470e0a6de7db9ba0ba115845566f738cc5124255b472347b5927565 alpSendTxid, // timeFirstSeen 1300000000, blockheight 102 | e623ab8971c93fa1a831a4310da65554c8dfd811c16cd5d41c6612268cb5dd5f alpMultiTxid, // timeFirstSeen 1300000000, blockheight 102 | e3c47f14d7ba3ab9a6f32a0fa8fcac41d06d3af595ebb5bab77ad03633a52eba diff --git a/modules/chronik-client/test/integration/token_slp_fungible.ts b/modules/chronik-client/test/integration/token_slp_fungible.ts --- a/modules/chronik-client/test/integration/token_slp_fungible.ts +++ b/modules/chronik-client/test/integration/token_slp_fungible.ts @@ -187,6 +187,28 @@ // Normal status expect(slpGenesis.tokenStatus).to.eql('TOKEN_STATUS_NORMAL'); + + // We can get token info of an slp token from the mempool + const slpGenesisMempoolInfo = await chronik.token(slpGenesisTxid); + expect(slpGenesisMempoolInfo).to.deep.equal({ + tokenId: slpGenesisTxid, + timeFirstSeen: '1300000000', + tokenType: { + protocol: 'SLP', + type: 'SLP_TOKEN_TYPE_FUNGIBLE', + number: 1, + }, + // We get hash in GenesisInfo for SLP + // We do not get mintVaultScripthash for non-SLP_MINT_VAULT + // We do not get data or authPubkey keys in GenesisInfo for non-ALP + genesisInfo: { + tokenTicker: 'SLPTEST', + tokenName: 'Test SLP Token 3', + url: 'http://example/slp', + hash: '7878787878787878787878787878787878787878787878787878787878787878', + decimals: 4, + }, + }); }); it('Gets an SLP fungible mint tx from the mempool', async () => { const chronikUrl = await chronik_url; @@ -395,6 +417,12 @@ const chronikUrl = await chronik_url; const chronik = new ChronikClientNode(chronikUrl); + // Now that we have a block, we get a block key from token info + const slpGenesisConfirmedInfo = await chronik.token(slpGenesisTxid); + expect(typeof slpGenesisConfirmedInfo.block !== 'undefined').to.eql( + true, + ); + const blockTxs = await chronik.blockTxs(CHAIN_INIT_HEIGHT + 2); // Clone as we will use blockTxs.txs later diff --git a/modules/chronik-client/test/integration/token_slp_mint_vault.ts b/modules/chronik-client/test/integration/token_slp_mint_vault.ts --- a/modules/chronik-client/test/integration/token_slp_mint_vault.ts +++ b/modules/chronik-client/test/integration/token_slp_mint_vault.ts @@ -240,6 +240,29 @@ // Normal status expect(slpVaultGenesis.tokenStatus).to.eql('TOKEN_STATUS_NORMAL'); + + // We can get token info of an slp vault token from the mempool + const slpGenesisMempoolInfo = await chronik.token(slpVaultGenesisTxid); + expect(slpGenesisMempoolInfo).to.deep.equal({ + tokenId: slpVaultGenesisTxid, + timeFirstSeen: '1300000000', + tokenType: { + protocol: 'SLP', + type: 'SLP_TOKEN_TYPE_MINT_VAULT', + number: 2, + }, + // We get mintVaultScripthash in GenesisInfo for SLP MINT VAULT + // We get hash in GenesisInfo for SLP + // We do not get data or authPubkey keys in GenesisInfo for non-ALP + genesisInfo: { + tokenTicker: 'SLPVAULT', + tokenName: '0', + url: '0', + hash: '7878787878787878787878787878787878787878787878787878787878787878', + mintVaultScripthash: '28e2146de5a061bf57845a04968d89cbdab733e3', + decimals: 0, + }, + }); }); it('Gets a badly constructed SLP v2 Vault Mint tx from the mempool', async () => { const chronikUrl = await chronik_url; diff --git a/modules/chronik-client/test/integration/token_slp_nft1.ts b/modules/chronik-client/test/integration/token_slp_nft1.ts --- a/modules/chronik-client/test/integration/token_slp_nft1.ts +++ b/modules/chronik-client/test/integration/token_slp_nft1.ts @@ -187,6 +187,28 @@ // Normal status expect(slpGenesis.tokenStatus).to.eql('TOKEN_STATUS_NORMAL'); + + // We can get token info of an slp nft1 from the mempool + const slpGenesisMempoolInfo = await chronik.token(slpGenesisTxid); + + // We do not get mintVaultScripthash for non-SLP_MINT_VAULT + // We do not get data or authPubkey keys in GenesisInfo for non-ALP + expect(slpGenesisMempoolInfo).to.deep.equal({ + tokenId: slpGenesisTxid, + timeFirstSeen: '1300000000', + tokenType: { + protocol: 'SLP', + type: 'SLP_TOKEN_TYPE_NFT1_GROUP', + number: 129, + }, + genesisInfo: { + tokenTicker: 'SLP NFT GROUP', + tokenName: 'Slp NFT GROUP token', + url: 'http://slp.nft', + hash: '7878787878787878787878787878787878787878787878787878787878787878', + decimals: 4, + }, + }); }); it('Gets an SLP NFT1 mint tx from the mempool', async () => { const chronikUrl = await chronik_url; @@ -359,6 +381,29 @@ slpChildGenesisTxid = await get_slp_nft1_child_genesis1_txid; + // We can get token info of an slp nft1 child genesis + const slpChildGenesisMempoolInfo = await chronik.token( + slpChildGenesisTxid, + ); + // We do not get mintVaultScripthash, data, or authPubkey keys in GenesisInfo for SLP NFT1 + expect(slpChildGenesisMempoolInfo).to.deep.equal({ + tokenId: slpChildGenesisTxid, + timeFirstSeen: '1300000000', + tokenType: { + protocol: 'SLP', + type: 'SLP_TOKEN_TYPE_NFT1_CHILD', + number: 65, + }, + genesisInfo: { + tokenTicker: 'SLP NFT CHILD', + tokenName: 'Slp NFT CHILD token', + url: '', + // We get hash even if blank because SLP tokens can have this field + hash: '', + decimals: 0, + }, + }); + slpChildGenesis = await chronik.tx(slpChildGenesisTxid); // We get expected inputs including expected Token data @@ -443,6 +488,18 @@ const blockTxs = await chronik.blockTxs(CHAIN_INIT_HEIGHT + 2); + // Now that we have a block, we get a block key from token info + const slpGenesisConfirmedInfo = await chronik.token(slpGenesisTxid); + expect(typeof slpGenesisConfirmedInfo.block !== 'undefined').to.eql( + true, + ); + const slpChildGenesisConfirmedInfo = await chronik.token( + slpChildGenesisTxid, + ); + expect( + typeof slpChildGenesisConfirmedInfo.block !== 'undefined', + ).to.eql(true); + // Clone as we will use blockTxs.txs later const txsFromBlock = JSON.parse(JSON.stringify(blockTxs.txs)); diff --git a/test/functional/setup_scripts/chronik-client_token_alp.py b/test/functional/setup_scripts/chronik-client_token_alp.py --- a/test/functional/setup_scripts/chronik-client_token_alp.py +++ b/test/functional/setup_scripts/chronik-client_token_alp.py @@ -308,9 +308,10 @@ yield True self.log.info("Step 7: Send another ALP tx in the mempool") - # Another ALP Send tx - alp_send_two_tx = CTransaction() - alp_send_two_tx.vin = [ + # Another ALP Genesis tx with non-utf8 genesis info + # we confirm this does not crash chronik-client token info endpoint + alp_nonutf8_genesis_tx = CTransaction() + alp_nonutf8_genesis_tx.vin = [ CTxIn( COutPoint(int(another_alp_genesis_tx_txid, 16), 4), SCRIPTSIG_OP_TRUE, @@ -320,26 +321,40 @@ SCRIPTSIG_OP_TRUE, ), ] - alp_send_two_tx.vout = [ + # Genesis with nonstandard token ticker and name to test how the client handles this + # token_name and token_url fields from coinbase of block 833028 + alp_nonutf8_genesis_tx.vout = [ alp_opreturn( - alp_send( - token_id=alp_genesis_tx_txid, - output_amounts=[10, 1], - ), + alp_genesis( + token_ticker=b"TEST", + token_name=bytes.fromhex("0304b60c048de8d9650881002834d6490000"), + url=bytes.fromhex( + "00fabe6d6d6f5486f62c703086014607f5bed91d093b092a8faf5ac882a0ccf462682a22f002" + ), + data=b"Token Data", + auth_pubkey=b"Token Pubkey", + decimals=4, + mint_amounts=[10, 20, 30, 0], + num_batons=2, + ) ), + CTxOut(10000, P2SH_OP_TRUE), + CTxOut(546, P2SH_OP_TRUE), + CTxOut(546, P2SH_OP_TRUE), CTxOut(coinvalue - 300000, P2SH_OP_TRUE), + CTxOut(5000, P2SH_OP_TRUE), CTxOut(546, P2SH_OP_TRUE), ] - alp_send_two_tx_txid = node.sendrawtransaction( - alp_send_two_tx.serialize().hex() + alp_nonutf8_genesis_tx_txid = node.sendrawtransaction( + alp_nonutf8_genesis_tx.serialize().hex() ) - send_ipc_message({"alp_send_two_txid": alp_send_two_tx_txid}) + send_ipc_message({"alp_nonutf8_genesis_txid": alp_nonutf8_genesis_tx_txid}) # Another ALP Mint tx alp_mint_two_tx = CTransaction() alp_mint_two_tx.vin = [ - CTxIn( - COutPoint(int(alp_send_two_tx_txid, 16), 1), + CTxIn( + COutPoint(int(alp_nonutf8_genesis_tx_txid, 16), 4), SCRIPTSIG_OP_TRUE, ), CTxIn(