net#Server TypeScript Examples

The following examples show how to use net#Server. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.ts    From common-ts with MIT License 6 votes vote down vote up
createMetricsServer = (options: MetricsServerOptions): Server => {
  const logger = options.logger.child({ component: 'MetricsServer' })

  const app = express()

  app.get(options.route || '/metrics', async (_, res) => {
    res.set('Content-Type', options.registry.contentType)
    res.status(200).send(await options.registry.metrics())
  })

  const port = options.port || 7300
  const server = app.listen(port, () => {
    logger.debug('Listening on port', { port })
  })

  return server
}
Example #2
Source File: WebSocketsManager.ts    From web with MIT License 6 votes vote down vote up
constructor(server: Server) {
    super();

    this.webSocketServer = new WebSocket.Server({
      noServer: true,
      path: `/${NAME_WEB_SOCKET_API}`,
    });
    this.webSocketServer.on('connection', webSocket => {
      this.openSockets.add(webSocket);
      webSocket.on('close', () => {
        this.openSockets.delete(webSocket);
      });

      webSocket.on('message', rawData => {
        try {
          const data = JSON.parse(rawData.toString());
          if (!data.type) {
            throw new Error('Missing property "type".');
          }
          this.emit('message', { webSocket, data });
        } catch (error) {
          console.error('Failed to parse websocket event received from the browser: ', rawData);
          console.error(error);
        }
      });
    });

    server.on('upgrade', (request, socket, head) => {
      this.webSocketServer.handleUpgrade(request, socket, head, ws => {
        this.webSocketServer.emit('connection', ws, request);
      });
    });
  }
Example #3
Source File: DevServer.test.ts    From web with MIT License 6 votes vote down vote up
it('calls serverStart on plugin hook on start', async () => {
  let startArgs: ServerStartParams;
  const { server } = await createTestServer({
    plugins: [
      {
        name: 'test',
        serverStart(args) {
          startArgs = args;
        },
      },
    ],
  });

  expect(startArgs!).to.exist;
  expect(startArgs!.app).to.be.an.instanceOf(Koa);
  expect(startArgs!.server).to.be.an.instanceOf(Server);
  expect(startArgs!.fileWatcher).to.be.an.instanceOf(FSWatcher);
  expect(startArgs!.config).to.be.an('object');

  server.stop();
});
Example #4
Source File: mqttAcceptor.ts    From regax with MIT License 6 votes vote down vote up
constructor(
    protected readonly opts: AcceptorOpts,
    protected readonly dispatcher: AcceptorDispacher
  ) {
    super()
    this.server = new Server()
    if (opts.logger) this.logger = opts.logger
    if (opts.rpcDebugLog) this.rpcDebugLog = true
  }
Example #5
Source File: server.ts    From cloudmusic-vscode with MIT License 5 votes vote down vote up
private static _server: Server;
Example #6
Source File: server.ts    From cloudmusic-vscode with MIT License 5 votes vote down vote up
private static _server: Server;
Example #7
Source File: integration.test.ts    From sse-z with MIT License 5 votes vote down vote up
describe("basic usage", () => {
  let port: number;
  let server: Server;

  beforeAll(async () => {
    const app = express();

    app.get("/sse", (req, res) => {
      res.writeHead(200, {
        "Content-Type": "text/event-stream",
        Connection: "keep-alive",
        "Cache-Control": "no-cache",
      });

      let counter = 0;
      const timer = setInterval(() => {
        counter++;
        res.write(`data: ${counter}\n\n`);
      }, 1000);

      req.on("close", () => {
        clearInterval(timer);
      });
    });

    port = await getPort();
    server = app.listen(port);
  });

  afterAll(async () => {
    await new Promise((resolve) => server.close(resolve));
  });

  test("subscribes and unsubscribes successfully", async () => {
    const results: string[] = [];
    const eventEmitter = new EventEmitter();
    const subscription = new Subscription({
      url: `http://localhost:${port}/sse`,
      onNext: (data) => {
        results.push(data);
        if (results.length === 3) {
          eventEmitter.emit("done");
        }
      },
    });
    await new Promise((resolve) => eventEmitter.on("done", resolve));
    subscription.unsubscribe();
    expect(results).toEqual(["1", "2", "3"]);
  });
});
Example #8
Source File: validate-construction.ts    From stacks-blockchain-api with GNU General Public License v3.0 5 votes vote down vote up
describe('Rosetta API', () => {
  let db: PgDataStore;
  let client: PoolClient;
  let eventServer: Server;
  let api: ApiServer;
  let rosettaOutput: any;

  beforeAll(async () => {
    process.env.PG_DATABASE = 'postgres';
    await cycleMigrations();
    db = await PgDataStore.connect({ usageName: 'tests' });
    client = await db.pool.connect();
    eventServer = await startEventServer({ datastore: db, chainId: ChainID.Testnet });
    api = await startApiServer({ datastore: db, chainId: ChainID.Testnet });

    // build rosetta-cli container
    await compose.buildOne('rosetta-cli', {
      cwd: path.join(__dirname, '../../'),
      log: true,
      composeOptions: [
        '-f',
        'docker/docker-compose.dev.rosetta-cli.yml',
        '--env-file',
        'src/tests-rosetta-cli-construction/envs/env.construction',
      ],
    });
    // start cli container
    void compose.upOne('rosetta-cli', {
      cwd: path.join(__dirname, '../../'),
      log: true,
      composeOptions: [
        '-f',
        'docker/docker-compose.dev.rosetta-cli.yml',
        '--env-file',
        'src/tests-rosetta-cli-construction/envs/env.construction',
      ],
      commandOptions: ['--abort-on-container-exit'],
    });

    await waitForBlock(api);

    await transferStx(recipientAdd1, 1000000000, sender1.privateKey, api);
    await transferStx(recipientAdd1, 1000000000, sender1.privateKey, api);
    await transferStx(recipientAdd1, 1000000000, sender1.privateKey, api);
    await transferStx(recipientAdd1, 1000000000, sender1.privateKey, api);
    await transferStx(recipientAdd1, 1000000000, sender1.privateKey, api);

    // Wait on rosetta-cli to finish output
    while (!rosettaOutput) {
      if (fs.existsSync('docker/rosetta-output-construction/rosetta-cli-output-const.json')) {
        rosettaOutput = require('../../docker/rosetta-output-construction/rosetta-cli-output-const.json');
      } else {
        await timeout(1000);
      }
    }
  });

  it('check transaction confirmed', () => {
    expect(rosettaOutput.stats.transactions_confirmed).toBeGreaterThan(1);
  });

  afterAll(async () => {
    await new Promise(resolve => eventServer.close(() => resolve(true)));
    await api.terminate();
    client.release();
    await db?.close();
    await runMigrations(undefined, 'down');
  });
});
Example #9
Source File: DevServer.ts    From web with MIT License 5 votes vote down vote up
public server: Server;
Example #10
Source File: WebSocketsManager.ts    From web with MIT License 5 votes vote down vote up
public webSocketServer: WebSocket.Server;
Example #11
Source File: mqttAcceptor.ts    From regax with MIT License 5 votes vote down vote up
protected server: Server
Example #12
Source File: impl.ts    From kassette with MIT License 4 votes vote down vote up
////////////////////////////////////////////////////////////////////////////////
// Server
// FIXME 2018-09-25T14:00:24+02:00
// Multiple requests will be received in parallel, leading to a messed up output in the console...
// they should be queued and processed one by one to avoid that
////////////////////////////////////////////////////////////////////////////////

/** Spawns the server */
export async function spawnServer({ configuration, root }: ApplicationData): Promise<Server> {
  const server = createServer(async (request, response) => {
    const mock = new Mock({
      options: { root, userConfiguration: configuration },
      request: new Request(request, await readAll(request)),
      response: new Response(response),
    });

    logInfo({
      timestamp: true,
      message: CONF.messages.handlingRequest,
      data: `${request.method} ${request.url}`,
    });

    await configuration.hook.value({ mock, console: getConsole() });
    await mock.process();
  });

  const tlsManager = new TLSManager({
    tlsCAKeyPath: configuration.tlsCAKeyPath.value,
    tlsKeySize: configuration.tlsKeySize.value,
    root,
  });
  await tlsManager.init();

  const handleSocket = (socket: Socket) => {
    socket.once('data', async (data) => {
      socket.pause();
      socket.unshift(data);
      // cf https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155
      // HTTPS:
      if (data.readUInt8(0) === 22) {
        socket = await tlsManager.process(socket);
        setConnectionProtocol(socket, 'https');
      } else {
        await Promise.resolve();
        setConnectionProtocol(socket, 'http');
      }
      server.emit('connection', socket);
      socket.resume();
    });
    socket.on('error', (exception) => logError({ message: CONF.messages.socketError, exception }));
  };

  server.on('connect', async (request: IncomingMessage, socket: Socket, data: Buffer) => {
    if (data.length > 0) {
      socket.unshift(data);
    }
    const api = new ProxyConnectAPI(request, configuration.proxyConnectMode.value, handleSocket);
    logInfo({
      timestamp: true,
      message: CONF.messages.handlingRequest,
      data: `${request.method} ${request.url}`,
    });
    await configuration.onProxyConnect.value(api);
    api.process();
  });

  // server that can receive both http and https connections
  const netServer = createNetServer(handleSocket);

  netServer.listen(configuration.port.value, configuration.hostname.value, function (this: Socket) {
    const port = (this.address() as AddressInfo).port;
    configuration.onListen.value({ port });

    logInfo({ message: CONF.messages.listening, data: port });
    logSeparator();
  });

  netServer.on('close', () => configuration.onExit.value());

  return netServer;
}
Example #13
Source File: bns-integration-tests.ts    From stacks-blockchain-api with GNU General Public License v3.0 4 votes vote down vote up
describe('BNS integration tests', () => {
  let db: PgDataStore;
  let client: PoolClient;
  let eventServer: Server;
  let api: ApiServer;

  function standByForTx(expectedTxId: string): Promise<DbTx> {
    const broadcastTx = new Promise<DbTx>(resolve => {
      const listener: (txId: string) => void = async txId => {
        const dbTxQuery = await api.datastore.getTx({ txId: txId, includeUnanchored: true });
        if (!dbTxQuery.found) {
          return;
        }
        const dbTx = dbTxQuery.result as DbTx;
        if (
          dbTx.tx_id === expectedTxId &&
          (dbTx.status === DbTxStatus.Success ||
            dbTx.status === DbTxStatus.AbortByResponse ||
            dbTx.status === DbTxStatus.AbortByPostCondition)
        ) {
          api.datastore.removeListener('txUpdate', listener);
          resolve(dbTx);
        }
      };
      api.datastore.addListener('txUpdate', listener);
    });

    return broadcastTx;
  }
  function standbyBnsName(expectedTxId: string): Promise<string> {
    const broadcastTx = new Promise<string>(resolve => {
      const listener: (txId: string) => void = txId => {
        if (txId === expectedTxId) {
          api.datastore.removeListener('nameUpdate', listener);
          resolve(txId);
        }
      };
      api.datastore.addListener('nameUpdate', listener);
    });

    return broadcastTx;
  }
  async function getContractTransaction(txOptions: SignedContractCallOptions, zonefile?: string) {
    const transaction = await makeContractCall(txOptions);
    const body: {tx: string, attachment?: string} = {
      tx: transaction.serialize().toString('hex'),
    };
    if(zonefile) body.attachment = Buffer.from(zonefile).toString('hex');
    const apiResult = await fetch(network.getBroadcastApiUrl(), {
      method: 'post',
      body: JSON.stringify(body),
      headers: { 'Content-Type': 'application/json' },
    });
    const submitResult = await apiResult.json();
    const expectedTxId = '0x' + transaction.txid();
    const result = await standByForTx(expectedTxId);
    if (result.status != 1) logger.error('name-import error');
    await standbyBnsName(expectedTxId);
    return transaction;
  }
  async function namespacePreorder(namespaceHash: Buffer, testnetKey: TestnetKey) {
    const txOptions: SignedContractCallOptions = {
      contractAddress: deployedTo,
      contractName: deployedName,
      functionName: 'namespace-preorder',
      functionArgs: [bufferCV(namespaceHash), uintCV(64000000000)],
      senderKey: testnetKey.pkey,
      validateWithAbi: true,
      postConditions: [makeStandardSTXPostCondition(testnetKey.address, FungibleConditionCode.GreaterEqual, new BigNum(1))],
      network,
      anchorMode: AnchorMode.Any
    };

    const transaction = await makeContractCall(txOptions);
    await broadcastTransaction(transaction, network);
    const preorder = await standByForTx('0x' + transaction.txid());
    if (preorder.status != 1) logger.error('Namespace preorder error');

    return transaction;
  }
  async function namespaceReveal(namespace: string, salt: Buffer, testnetKey: TestnetKey, expiration: number) {
    const revealTxOptions: SignedContractCallOptions = {
      contractAddress: deployedTo,
      contractName: deployedName,
      functionName: 'namespace-reveal',
      functionArgs: [
        bufferCV(Buffer.from(namespace)),
        bufferCV(salt),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(1),
        uintCV(expiration), //this number is set to expire the name before calling name-revewal
        standardPrincipalCV(testnetKey.address),
      ],
      senderKey: testnetKey.pkey,
      validateWithAbi: true,
      network,
      anchorMode: AnchorMode.Any
    };
    const revealTransaction = await makeContractCall(revealTxOptions);
    await broadcastTransaction(revealTransaction, network);
    const reveal = await standByForTx('0x' + revealTransaction.txid());
    if (reveal.status != 1) logger.error('Namespace Reveal Error');
    return revealTransaction;
  }
  async function initiateNamespaceNetwork(namespace: string, salt: Buffer, namespaceHash: Buffer, testnetKey: TestnetKey, expiration: number){
    while (true) {
      try {
        const preorderTransaction = await namespacePreorder(namespaceHash, testnetKey);

        const revealTransaction = await namespaceReveal(namespace, salt, testnetKey, expiration);

        break;
      } catch (e) {
        console.log('error connection', e);
      }
    }
  }
  async function namespaceReady(namespace: string, pkey: string) {
    const txOptions = {
      contractAddress: deployedTo,
      contractName: deployedName,
      functionName: 'namespace-ready',
      functionArgs: [bufferCV(Buffer.from(namespace))],
      senderKey: pkey,
      validateWithAbi: true,
      network,
      anchorMode: AnchorMode.Any
    };

    const transaction = await makeContractCall(txOptions);
    await broadcastTransaction(transaction, network);

    const readyResult = await standByForTx('0x' + transaction.txid());
    if (readyResult.status != 1) logger.error('namespace-ready error');

    return transaction;
  }
  async function nameImport(namespace: string, zonefile: string, name: string, testnetKey: TestnetKey) {
    const txOptions = {
      contractAddress: deployedTo,
      contractName: deployedName,
      functionName: 'name-import',
      functionArgs: [
        bufferCV(Buffer.from(namespace)),
        bufferCV(Buffer.from(name)),
        standardPrincipalCV(testnetKey.address),
        bufferCV(hash160(Buffer.from(zonefile))),
      ],
      senderKey: testnetKey.pkey,
      validateWithAbi: true,
      network,
      anchorMode: AnchorMode.Any
    };
    return await getContractTransaction(txOptions, zonefile);
  }
  async function nameUpdate(namespace: string, zonefile: string, name: string, pkey: string) {
    const txOptions = {
      contractAddress: deployedTo,
      contractName: deployedName,
      functionName: 'name-update',
      functionArgs: [
        bufferCV(Buffer.from(namespace)),
        bufferCV(Buffer.from(name)),
        bufferCV(hash160(Buffer.from(zonefile))),
      ],
      senderKey: pkey,
      validateWithAbi: true,
      network,
      anchorMode: AnchorMode.Any
    };

    return await getContractTransaction(txOptions, zonefile);
  }
  async function namePreorder(namespace: string, saltName: string, testnetKey: TestnetKey, name: string) {
    const postConditions = [
      makeStandardSTXPostCondition(testnetKey.address, FungibleConditionCode.GreaterEqual, new BigNum(1)),
    ];
    const fqn = `${name}.${namespace}${saltName}`;
    const nameSaltedHash = hash160(Buffer.from(fqn));
    const preOrderTxOptions: SignedContractCallOptions = {
      contractAddress: deployedTo,
      contractName: deployedName,
      functionName: 'name-preorder',
      functionArgs: [bufferCV(nameSaltedHash), uintCV(64000000000)],
      senderKey: testnetKey.pkey,
      validateWithAbi: true,
      postConditions: postConditions,
      network,
      anchorMode: AnchorMode.Any
    };

    const preOrderTransaction = await makeContractCall(preOrderTxOptions);
    await broadcastTransaction(preOrderTransaction, network);
    const preorderResult = await standByForTx('0x' + preOrderTransaction.txid());
    return preOrderTransaction;
  }
  async function nameRegister(namespace: string, saltName: string, zonefile: string, testnetKey: TestnetKey, name: string) {
    await namePreorder(namespace, saltName, testnetKey, name);
    const txOptions = {
      contractAddress: deployedTo,
      contractName: deployedName,
      functionName: 'name-register',
      functionArgs: [
        bufferCV(Buffer.from(namespace)),
        bufferCV(Buffer.from(name)),
        bufferCV(Buffer.from(saltName)),
        bufferCV(hash160(Buffer.from(zonefile))),
      ],
      senderKey: testnetKey.pkey,
      validateWithAbi: true,
      network,
      anchorMode: AnchorMode.Any
    };
    return await getContractTransaction(txOptions, zonefile);
  }
  async function nameTransfer(namespace: string, name: string, testnetKey: TestnetKey) {
    const txOptions: SignedContractCallOptions = {
      contractAddress: deployedTo,
      contractName: deployedName,
      functionName: 'name-transfer',
      functionArgs: [
        bufferCV(Buffer.from(namespace)),
        bufferCV(Buffer.from(name)),
        standardPrincipalCV(testnetKey.address),
        noneCV(),
      ],
      senderKey: testnetKey.pkey,
      validateWithAbi: true,
      postConditionMode: PostConditionMode.Allow,
      anchorMode: AnchorMode.Any,
      network,
    };

    return await getContractTransaction(txOptions);
  }
  async function nameRevoke(namespace: string, name: string, pkey: string) {
    const txOptions: SignedContractCallOptions = {
      contractAddress: deployedTo,
      contractName: deployedName,
      functionName: 'name-revoke',
      functionArgs: [bufferCV(Buffer.from(namespace)), bufferCV(Buffer.from(name))],
      senderKey: pkey,
      validateWithAbi: true,
      network,
      anchorMode: AnchorMode.Any
    };
    return await getContractTransaction(txOptions);
  }
  async function nameRenewal(namespace: string, zonefile: string, pkey: string, name: string) {
    const txOptions: SignedContractCallOptions = {
      contractAddress: deployedTo,
      contractName: deployedName,
      functionName: 'name-renewal',
      functionArgs: [
        bufferCV(Buffer.from(namespace)),
        bufferCV(Buffer.from(name)),
        uintCV(2560000),
        noneCV(),
        someCV(bufferCV(hash160(Buffer.from(zonefile)))),
      ],
      senderKey: pkey,
      validateWithAbi: true,
      network,
      anchorMode: AnchorMode.Any
    };
    return await getContractTransaction(txOptions);
  }

  beforeAll(async () => {
    process.env.PG_DATABASE = 'postgres';
    await cycleMigrations();
    db = await PgDataStore.connect({ usageName: 'tests' });
    client = await db.pool.connect();
    eventServer = await startEventServer({ datastore: db, chainId: ChainID.Testnet, httpLogLevel: 'silly' });
    api = await startApiServer({ datastore: db, chainId: ChainID.Testnet, httpLogLevel: 'silly' });

    const block = new TestBlockBuilder().build();
    await db.update(block);
  });

  test('name-import/ready/update contract call', async () => {
    const namespace = 'name-import';
    const name = 'alice';
    const importZonefile = `$ORIGIN ${name}.${namespace}\n$TTL 3600\n_http._tcp IN URI 10 1 "https://blockstack.s3.amazonaws.com/${name}.${namespace}"\n`;
    const namespaceHash = hash160(Buffer.concat([Buffer.from(namespace), salt]));
    const testnetKey = { pkey: testnetKeys[0].secretKey, address: testnetKeys[0].stacksAddress};

    // initalizing namespace network - preorder and reveal
    await initiateNamespaceNetwork(namespace, salt, namespaceHash, testnetKey, 12);

    // testing name import
    await nameImport(namespace, importZonefile, name, testnetKey);

    const importQuery = await db.getName({ name: `${name}.${namespace}`, includeUnanchored: false, chainId: network.chainId });
    const importQuery1 = await supertest(api.server).get(`/v1/names/${name}.${namespace}`);
    expect(importQuery1.status).toBe(200);
    expect(importQuery1.type).toBe('application/json');
    expect(importQuery.found).toBe(true);
    if (importQuery.found) {
      expect(importQuery.result.zonefile).toBe(importZonefile);
    }

    // testing namespace ready
    await namespaceReady(namespace, testnetKey.pkey);

    const readyQuery1 = await supertest(api.server).get('/v1/namespaces');
    const readyResult = JSON.parse(readyQuery1.text);
    expect(readyResult.namespaces.includes(namespace)).toBe(true);
  });

  test('name-update contract call', async () => {
    const namespace = 'name-update';
    const name = 'update';
    const importZonefile = `$ORIGIN ${name}.${namespace}\n$TTL 3600\n_http._tcp IN URI 10 1 "https://blockstack.s3.amazonaws.com/${name}.${namespace}"\n`;
    const namespaceHash = hash160(Buffer.concat([Buffer.from(namespace), salt]));
    const testnetKey = { pkey: testnetKeys[1].secretKey, address: testnetKeys[1].stacksAddress};

    // initalizing namespace network - preorder and reveal
    await initiateNamespaceNetwork(namespace, salt, namespaceHash, testnetKey, 12);

    // testing name import
    await nameImport(namespace, importZonefile, name, testnetKey);

    await namespaceReady(namespace, testnetKey.pkey);

    // testing name update 1
    let zonefile = `$TTL 3600
    1yeardaily TXT "owner=1MwPD6dH4fE3gQ9mCov81L1DEQWT7E85qH" "seqn=0" "parts=1" "zf0=JE9SSUdJTiAxeWVhcmRhaWx5CiRUVEwgMzYwMApfaHR0cC5fdGNwIFVSSSAxMCAxICJodHRwczovL3BoLmRvdHBvZGNhc3QuY28vMXllYXJkYWlseS9oZWFkLmpzb24iCg=="
    _http._tcp URI 10 1 "https://dotpodcast.co/"`;

    try {
      // testing name update
      await nameUpdate(namespace, zonefile, name, testnetKey.pkey);
      const query1 = await supertest(api.server).get(`/v1/names/1yeardaily.${name}.${namespace}`);
      expect(query1.status).toBe(200);
      expect(query1.type).toBe('application/json');
      const query2 = await db.getSubdomain({ subdomain: `1yeardaily.${name}.${namespace}`, includeUnanchored: false });
      expect(query2.found).toBe(true);
      if(query2.result)
        expect(query2.result.resolver).toBe('');

      const query3 = await supertest(api.server).get(`/v1/names/${name}.${namespace}`);
      expect(query3.status).toBe(200);
      expect(query3.type).toBe('application/json');
      expect(query3.body.zonefile).toBe(zonefile);
    } catch (err: any) {
      throw new Error('Error post transaction: ' + err.message);
    }
    // testing name update 2
    zonefile = `$TTL 3600
    1yeardaily TXT "owner=1MwPD6dH4fE3gQ9mCov81L1DEQWT7E85qH" "seqn=0" "parts=1" "zf0=JE9SSUdJTiAxeWVhcmRhaWx5CiRUVEwgMzYwMApfaHR0cC5fdGNwIFVSSSAxMCAxICJodHRwczovL3BoLmRvdHBvZGNhc3QuY28vMXllYXJkYWlseS9oZWFkLmpzb24iCg=="Í
    2dopequeens TXT "owner=1MwPD6dH4fE3gQ9mCov81L1DEQWT7E85qH" "seqn=0" "parts=1" "zf0=JE9SSUdJTiAyZG9wZXF1ZWVucwokVFRMIDM2MDAKX2h0dHAuX3RjcCBVUkkgMTAgMSAiaHR0cHM6Ly9waC5kb3Rwb2RjYXN0LmNvLzJkb3BlcXVlZW5zL2hlYWQuanNvbiIK"
    10happier TXT "owner=1MwPD6dH4fE3gQ9mCov81L1DEQWT7E85qH" "seqn=0" "parts=1" "zf0=JE9SSUdJTiAxMGhhcHBpZXIKJFRUTCAzNjAwCl9odHRwLl90Y3AgVVJJIDEwIDEgImh0dHBzOi8vcGguZG90cG9kY2FzdC5jby8xMGhhcHBpZXIvaGVhZC5qc29uIgo="
    31thoughts TXT "owner=1MwPD6dH4fE3gQ9mCov81L1DEQWT7E85qH" "seqn=0" "parts=1" "zf0=JE9SSUdJTiAzMXRob3VnaHRzCiRUVEwgMzYwMApfaHR0cC5fdGNwIFVSSSAxMCAxICJodHRwczovL3BoLmRvdHBvZGNhc3QuY28vMzF0aG91Z2h0cy9oZWFkLmpzb24iCg=="
    359 TXT "owner=1MwPD6dH4fE3gQ9mCov81L1DEQWT7E85qH" "seqn=0" "parts=1" "zf0=JE9SSUdJTiAzNTkKJFRUTCAzNjAwCl9odHRwLl90Y3AgVVJJIDEwIDEgImh0dHBzOi8vcGguZG90cG9kY2FzdC5jby8zNTkvaGVhZC5qc29uIgo="
    30for30 TXT "owner=1MwPD6dH4fE3gQ9mCov81L1DEQWT7E85qH" "seqn=0" "parts=1" "zf0=JE9SSUdJTiAzMGZvcjMwCiRUVEwgMzYwMApfaHR0cC5fdGNwIFVSSSAxMCAxICJodHRwczovL3BoLmRvdHBvZGNhc3QuY28vMzBmb3IzMC9oZWFkLmpzb24iCg=="
    excluded TXT "subdomain should not include"
    10minuteteacher TXT "owner=1MwPD6dH4fE3gQ9mCov81L1DEQWT7E85qH" "seqn=0" "parts=1" "zf0=JE9SSUdJTiAxMG1pbnV0ZXRlYWNoZXIKJFRUTCAzNjAwCl9odHRwLl90Y3AgVVJJIDEwIDEgImh0dHBzOi8vcGguZG90cG9kY2FzdC5jby8xMG1pbnV0ZXRlYWNoZXIvaGVhZC5qc29uIgo="
    36questionsthepodcastmusical TXT "owner=1MwPD6dH4fE3gQ9mCov81L1DEQWT7E85qH" "seqn=0" "parts=1" "zf0=JE9SSUdJTiAzNnF1ZXN0aW9uc3RoZXBvZGNhc3RtdXNpY2FsCiRUVEwgMzYwMApfaHR0cC5fdGNwIFVSSSAxMCAxICJodHRwczovL3BoLmRvdHBvZGNhc3QuY28vMzZxdWVzdGlvbnN0aGVwb2RjYXN0bXVzaWNhbC9oZWFkLmpzb24iCg=="
    _http._tcp URI 10 1 "https://dotpodcast.co/"`;
    await nameUpdate(namespace, zonefile, name, testnetKey.pkey);
    const query1 = await supertest(api.server).get(`/v1/names/2dopequeens.${name}.${namespace}`);
    expect(query1.status).toBe(200);
    expect(query1.type).toBe('application/json');

    const query2 = await db.getSubdomainsList({ page: 0, includeUnanchored: false });
    expect(
      query2.results.filter(function (value) {
        return value === `1yeardaily.${name}.${namespace}`;
      }).length
    ).toBe(1);
    const query3 = await supertest(api.server).get(`/v1/names/${name}.${namespace}`);
    expect(query3.status).toBe(200);
    expect(query3.type).toBe('application/json');
    expect(query3.body.zonefile).toBe(zonefile); //zone file updated of same name

    const query4 = await supertest(api.server).get(
      `/v1/names/36questionsthepodcastmusical.${name}.${namespace}`
    );
    expect(query4.status).toBe(200);

    const query5 = await supertest(api.server).get(`/v1/names/excluded.${name}.${namespace}`);
    expect(query5.status).toBe(404);
    expect(query5.type).toBe('application/json');

    // testing nameupdate 3
    zonefile = `$TTL 3600
    _http._tcp URI 10 1 "https://dotpodcast.co/"`;
    await nameUpdate(namespace, zonefile, name, testnetKey.pkey);

    try {
      const query6 = await supertest(api.server).get(`/v1/names/2dopequeens.${name}.${namespace}`); //check if previous sobdomains are still there
      expect(query6.status).toBe(200);
      expect(query6.type).toBe('application/json');
      const query7 = await db.getSubdomainsList({ page: 0, includeUnanchored: false });
      expect(query7.results).toContain(`1yeardaily.${name}.${namespace}`);
      const query8 = await supertest(api.server).get(`/v1/names/${name}.${namespace}`);
      expect(query8.status).toBe(200);
      expect(query8.type).toBe('application/json');
      expect(query8.body.zonefile).toBe(zonefile);
    } catch (err: any) {
      throw new Error('Error post transaction: ' + err.message);
    }
  });

  test('name-register/transfer contract call', async () => {
    const saltName = '0000';
    const name = 'bob';
    const namespace = 'name-register';
    const namespaceHash = hash160(Buffer.concat([Buffer.from(namespace), salt]));
    const zonefile = `$ORIGIN ${name}.${namespace}\n$TTL 3600\n_http._tcp IN URI 10 1 "https://blockstack.s3.amazonaws.com/${name}.${namespace}"\n`;
    const importZonefile = `$ORIGIN ${name}.${namespace}\n$TTL 3600\n_http._tcp IN URI 10 1 "https://blockstack.s3.amazonaws.com/${name}.${namespace}"\n`;
    const testnetKey = { pkey: testnetKeys[2].secretKey, address: testnetKeys[2].stacksAddress};
    // initializing namespace network 
    await initiateNamespaceNetwork(namespace, salt, namespaceHash, testnetKey, 12);
    await namespaceReady(namespace, testnetKey.pkey);

    // testing name register
    await nameRegister(namespace, saltName, zonefile, testnetKey, name);
    const query1 = await supertest(api.server).get(`/v1/names/${name}.${namespace}`);
    expect(query1.status).toBe(200);
    expect(query1.type).toBe('application/json');
    const query = await db.getName({ name: `${name}.${namespace}`, includeUnanchored: false, chainId: network.chainId });
    expect(query.found).toBe(true);
    if (query.found) {
      expect(query.result.zonefile).toBe(zonefile);
    }
    // testing name transfer
    const transferTestnetKey = { pkey: testnetKeys[2].secretKey, address: testnetKeys[3].stacksAddress};
    await nameTransfer(namespace, name, transferTestnetKey);

    try {
      const query1 = await supertest(api.server).get(`/v1/names/${name}.${namespace}`);
      expect(query1.status).toBe(200);
      expect(query1.type).toBe('application/json');
      expect(query1.body.zonefile).toBe('');
      expect(query1.body.status).toBe('name-transfer');
    } catch (err: any) {
      throw new Error('Error post transaction: ' + err.message);
    }
  });

  test('name-revoke contract call', async () => {
    //name revoke
    const namespace = 'name-revoke';
    const name = 'foo';
    const namespaceHash = hash160(Buffer.concat([Buffer.from(namespace), salt]));
    const testnetKey = { pkey: testnetKeys[4].secretKey, address: testnetKeys[4].stacksAddress};
    const zonefile = `$ORIGIN ${name}.${namespace}\n$TTL 3600\n_http._tcp IN URI 10 1 "https://blockstack.s3.amazonaws.com/${name}.${namespace}"\n`;
    
    // initializing namespace network
    await initiateNamespaceNetwork(namespace, salt, namespaceHash, testnetKey, 12);
    await nameImport(namespace, zonefile, name, testnetKey);
    await namespaceReady(namespace, testnetKey.pkey);

    // testing name revoke
    const transaction = await nameRevoke(namespace, name, testnetKey.pkey);
    const query1 = await supertest(api.server).get(`/v1/names/${name}.${namespace}`);
    expect(query1.status).toBe(200);
    expect(query1.type).toBe('application/json');
    expect(query1.body.status).toBe('name-revoke');
  });

  test('name-renewal contract call', async () => {
    const zonefile = `new zone file`;
    const namespace = 'name-renewal';
    const name = 'renewal';
    const namespaceHash = hash160(Buffer.concat([Buffer.from(namespace), salt]));
    const testnetKey = { pkey: testnetKeys[5].secretKey, address: testnetKeys[5].stacksAddress};
    
    // initializing namespace network
    await initiateNamespaceNetwork(namespace, salt, namespaceHash, testnetKey, 1);
    await nameImport(namespace, zonefile, name, testnetKey);
    await namespaceReady(namespace, testnetKey.pkey);

    //name renewal
    await nameRenewal(namespace, zonefile, testnetKey.pkey, name);
    try {
      const query1 = await supertest(api.server).get(`/v1/names/${name}.${namespace}`);
      expect(query1.status).toBe(200);
      expect(query1.type).toBe('application/json');
      expect(query1.body.zonefile).toBe(zonefile);
      expect(query1.body.status).toBe('name-renewal');
    } catch (err: any) {
      throw new Error('Error post transaction: ' + err.message);
    }
  });

  test('bns v1-import', async () => {
    await importV1BnsData(db, 'src/tests-bns/import-test-files');

    // test on-chain name import
    const query1 = await supertest(api.server).get(`/v1/names/zumrai.id`);
    expect(query1.status).toBe(200);
    expect(query1.type).toBe('application/json');
    expect(query1.body).toEqual({
      address: 'SP29EJ0SVM2TRZ3XGVTZPVTKF4SV1VMD8C0GA5SK5',
      blockchain: 'stacks',
      expire_block: 52595,
      last_txid: '',
      status: 'name-register',
      zonefile:
        '$ORIGIN zumrai.id\n$TTL 3600\n_http._tcp	IN	URI	10	1	"https://gaia.blockstack.org/hub/1EPno1VcdGx89ukN2we4iVpnFtkHzw8i5d/profile.json"\n\n',
      zonefile_hash: '853cd126478237bc7392e65091f7ffa5a1556a33',
    });

    // test subdomain import
    const query2 = await supertest(api.server).get(`/v1/names/flushreset.id.blockstack`);
    expect(query2.status).toBe(200);
    expect(query2.type).toBe('application/json');
    expect(query2.body).toEqual({
      address: 'SP2S2F9TCAT43KEJT02YTG2NXVCPZXS1426T63D9H',
      blockchain: 'stacks',
      last_txid: '',
      resolver: 'https://registrar.blockstack.org',
      status: 'registered_subdomain',
      zonefile:
        '$ORIGIN flushreset.id.blockstack\n$TTL 3600\n_http._tcp	IN	URI	10	1	"https://gaia.blockstack.org/hub/1HEznKZ7mK5fmibweM7eAk8SwRgJ1bWY92/profile.json"\n\n',
      zonefile_hash: '14dc091ebce8ea117e1276d802ee903cc0fdde81',
    });

    const dbquery = await db.getSubdomain({ subdomain: `flushreset.id.blockstack`, includeUnanchored: false });
    assert(dbquery.found)
    if (dbquery.result){
    expect(dbquery.result.name).toBe('id.blockstack');}
  });

  afterAll(async () => {
    await new Promise(resolve => eventServer.close(() => resolve(true)));
    await api.terminate();
    client.release();
    await db?.close();
    await runMigrations(undefined, 'down');
  });
});
Example #14
Source File: construction.ts    From stacks-blockchain-api with GNU General Public License v3.0 4 votes vote down vote up
describe('Rosetta API', () => {
  let db: PgDataStore;
  let client: PoolClient;
  let eventServer: Server;
  let api: ApiServer;

  function standByForTx(expectedTxId: string): Promise<DbTx> {
    const broadcastTx = new Promise<DbTx>(resolve => {
      const listener: (txId: string) => void = async txId => {
        const dbTxQuery = await api.datastore.getTx({ txId: txId, includeUnanchored: true });
        if (!dbTxQuery.found) {
          return;
        }
        const dbTx = dbTxQuery.result as DbTx;
        if (
          dbTx.tx_id === expectedTxId &&
          (dbTx.status === DbTxStatus.Success ||
            dbTx.status === DbTxStatus.AbortByResponse ||
            dbTx.status === DbTxStatus.AbortByPostCondition)
        ) {
          api.datastore.removeListener('txUpdate', listener);
          resolve(dbTx);
        }
      };
      api.datastore.addListener('txUpdate', listener);
    });

    return broadcastTx;
  }

  async function standByForPoxToBeReady(): Promise<void>{
    let tries = 0;
    while(true){
      try{
        tries++;
        await  new StacksCoreRpcClient().getPox();
        return Promise.resolve();
      }
      catch(error){
        console.log('Error getting pox info on try ' + tries, error);
        await timeout(500);
      }
    }
  }

  beforeAll(async () => {
    process.env.PG_DATABASE = 'postgres';
    process.env.STACKS_CHAIN_ID = '0x80000000';
    await cycleMigrations();
    db = await PgDataStore.connect({ usageName: 'tests' });
    client = await db.pool.connect();
    eventServer = await startEventServer({ datastore: db, chainId: ChainID.Testnet });
    api = await startApiServer({ datastore: db, chainId: ChainID.Testnet });
  });

  /* rosetta construction api tests below */

  test('construction/derive', async () => {
    const request: RosettaConstructionDeriveRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      public_key: {
        curve_type: 'secp256k1',
        hex_bytes: publicKeyToString(
          getPublicKey(createStacksPrivateKey(testnetKeys[0].secretKey))
        ),
      },
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/derive`)
      .send(request);

    expect(result.status).toBe(200);
    expect(result.type).toBe('application/json');

    const accountIdentifier: RosettaAccountIdentifier = {
      address: testnetKeys[0].stacksAddress,
    };
    const expectResponse: RosettaConstructionDeriveResponse = {
      account_identifier: accountIdentifier,
    };

    expect(JSON.parse(result.text)).toEqual(expectResponse);

    const request2 = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      public_key: {
        curve_type: 'this is an invalid curve type',
        hex_bytes: '025c13b2fc2261956d8a4ad07d481b1a3b2cbf93a24f992249a61c3a1c4de79c51',
      },
    };

    const result2 = await supertest(api.server)
      .post(`/rosetta/v1/construction/derive`)
      .send(request2);
    expect(result2.status).toBe(400);

    const expectedResponse2 = RosettaErrors[RosettaErrorsTypes.invalidCurveType];

    expect(JSON.parse(result2.text)).toEqual(expectedResponse2);

    const request3 = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      public_key: {
        curve_type: 'secp256k1',
        hex_bytes: 'this is an invalid public key',
      },
    };

    const result3 = await supertest(api.server)
      .post(`/rosetta/v1/construction/derive`)
      .send(request3);
    expect(result3.status).toBe(400);

    const expectedResponse3 = RosettaErrors[RosettaErrorsTypes.invalidPublicKey];

    expect(JSON.parse(result3.text)).toEqual(expectedResponse3);
  });

  test('construction/preprocess', async () => {
    const request: RosettaConstructionPreprocessRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      operations: [
        {
          operation_identifier: {
            index: 0,
            network_index: 0,
          },
          related_operations: [],
          type: 'token_transfer',
          account: {
            address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
            metadata: {},
          },
          amount: {
            value: '-500000',
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
        {
          operation_identifier: {
            index: 1,
            network_index: 0,
          },
          related_operations: [],
          type: 'token_transfer',
          account: {
            address: 'STDE7Y8HV3RX8VBM2TZVWJTS7ZA1XB0SSC3NEVH0',
            metadata: {},
          },
          amount: {
            value: '500000',
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
      ],
      metadata: {
        memo: 'SAMPLE MEMO',
      },
      max_fee: [
        {
          value: '12380898',
          currency: {
            symbol: 'STX',
            decimals: 6,
          },
          metadata: {},
        },
      ],
      suggested_fee_multiplier: 1,
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/preprocess`)
      .send(request);

    expect(result.status).toBe(200);
    expect(result.type).toBe('application/json');

    const expectResponse: RosettaConstructionPreprocessResponse = {
      options: {
        sender_address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
        type: 'token_transfer',
        suggested_fee_multiplier: 1,
        token_transfer_recipient_address: 'STDE7Y8HV3RX8VBM2TZVWJTS7ZA1XB0SSC3NEVH0',
        amount: '500000',
        symbol: 'STX',
        decimals: 6,
        max_fee: '12380898',
        memo: 'SAMPLE MEMO',
        size: 180,
      },
      required_public_keys: [
        {
          address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
        },
      ],
    };

    expect(JSON.parse(result.text)).toEqual(expectResponse);
  });

  test('construction/preprocess - failure', async () => {
    const request2 = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      operations: [
        {
          operation_identifier: {
            index: 0,
            network_index: 0,
          },
          related_operations: [],
          type: 'invalid operation type',
          account: {
            address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
            metadata: {},
          },
          amount: {
            value: '-500000',
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
        {
          operation_identifier: {
            index: 1,
            network_index: 0,
          },
          related_operations: [],
          type: 'token_transfer',
          account: {
            address: 'STDE7Y8HV3RX8VBM2TZVWJTS7ZA1XB0SSC3NEVH0',
            metadata: {},
          },
          amount: {
            value: '500000',
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
      ],
      metadata: {},
      max_fee: [
        {
          value: '12380898',
          currency: {
            symbol: 'STX',
            decimals: 6,
          },
          metadata: {},
        },
      ],
      suggested_fee_multiplier: 1,
    };

    const result2 = await supertest(api.server)
      .post(`/rosetta/v1/construction/preprocess`)
      .send(request2);
    expect(result2.status).toBe(400);

    const expectedResponse2 = RosettaErrors[RosettaErrorsTypes.invalidOperation];

    expect(JSON.parse(result2.text)).toEqual(expectedResponse2);
  });

  test('construction/metadata - success', async () => {
    const request: RosettaConstructionMetadataRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      options: {
        sender_address: testnetKeys[0].stacksAddress,
        type: 'token_transfer',
        suggested_fee_multiplier: 1,
        token_transfer_recipient_address: testnetKeys[1].stacksAddress,
        amount: '500000',
        symbol: 'STX',
        decimals: 6,
        max_fee: '12380898',
        size: 180,
        memo: 'SAMPLE MEMO',
      },
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/metadata`)
      .send(request);

    expect(result.status).toBe(200);
    expect(result.type).toBe('application/json');
    expect(JSON.parse(result.text)).toHaveProperty('metadata');
    expect(JSON.parse(result.text)).toHaveProperty('suggested_fee');
    expect(JSON.parse(result.text).suggested_fee[0].value).toBe('180');
    expect(JSON.parse(result.text).metadata.memo).toBe('SAMPLE MEMO');
  });

  test('construction/metadata - empty network identifier', async () => {
    const request = {
      options: {
        sender_address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
        type: 'token_transfer',
        token_transfer_recipient_address: 'STDE7Y8HV3RX8VBM2TZVWJTS7ZA1XB0SSC3NEVH0',
        amount: '500000',
        symbol: 'STX',
        decimals: 6,
        max_fee: '12380898',
        size: 180,
      },
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/metadata`)
      .send(request);

    expect(result.status).toBe(400);
    expect(result.type).toBe('application/json');

    const expectResponse = {
      code: 613,
      message: 'Network identifier object is null.',
      retriable: false,
      details: {
        message: "should have required property 'network_identifier'",
      },
    };

    expect(JSON.parse(result.text)).toEqual(expectResponse);
  });

  test('construction/metadata - invalid transfer type', async () => {
    const request: RosettaConstructionMetadataRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      options: {
        sender_address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
        type: 'token',
        token_transfer_recipient_address: 'STDE7Y8HV3RX8VBM2TZVWJTS7ZA1XB0SSC3NEVH0',
        amount: '500000',
        symbol: 'STX',
        decimals: 6,
        max_fee: '12380898',
        size: 180,
      },
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/metadata`)
      .send(request);

    expect(result.status).toBe(400);
    expect(result.type).toBe('application/json');

    const expectResponse = {
      code: 625,
      message: 'Invalid transaction type',
      retriable: false,
    };

    expect(JSON.parse(result.text)).toEqual(expectResponse);
  });

  test('construction/metadata - invalid sender address', async () => {
    const request: RosettaConstructionMetadataRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      options: {
        sender_address: 'abc',
        type: 'token_transfer',
        token_transfer_recipient_address: 'STDE7Y8HV3RX8VBM2TZVWJTS7ZA1XB0SSC3NEVH0',
        amount: '500000',
        symbol: 'STX',
        decimals: 6,
        fee: '-180',
        max_fee: '12380898',
        size: 180,
      },
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/metadata`)
      .send(request);

    expect(result.status).toBe(400);
    expect(result.type).toBe('application/json');

    const expectResponse = {
      code: 626,
      message: 'Invalid sender address',
      retriable: false,
    };

    expect(JSON.parse(result.text)).toEqual(expectResponse);
  });

  test('construction/metadata - invalid recipient address', async () => {
    const request: RosettaConstructionMetadataRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      options: {
        sender_address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
        type: 'token_transfer',
        token_transfer_recipient_address: 'xyz',
        amount: '500000',
        symbol: 'STX',
        decimals: 6,
        max_fee: '12380898',
        size: 180,
      },
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/metadata`)
      .send(request);

    expect(result.status).toBe(400);
    expect(result.type).toBe('application/json');

    const expectResponse = {
      code: 627,
      message: 'Invalid recipient address',
      retriable: false,
    };

    expect(JSON.parse(result.text)).toEqual(expectResponse);
  });

  test('construction/hash', async () => {
    const request: RosettaConstructionHashRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      signed_transaction:
        '0x80800000000400539886f96611ba3ba6cef9618f8c78118b37c5be000000000000000000000000000000b400017a33a91515ef48608a99c6adecd2eb258e11534a1acf66348f5678c8e2c8f83d243555ed67a0019d3500df98563ca31321c1a675b43ef79f146e322fe08df75103020000000000051a1ae3f911d8f1d46d7416bfbe4b593fd41eac19cb000000000007a12000000000000000000000000000000000000000000000000000000000000000000000',
    };

    const result = await supertest(api.server).post(`/rosetta/v1/construction/hash`).send(request);
    expect(result.status).toBe(200);

    const expectedResponse: RosettaConstructionHashResponse = {
      transaction_identifier: {
        hash: '0xf3b054a5fbae98f7f35e5e917b65759fc365a3e073f8af1c3b8d211b286fa74a',
      },
    };

    expect(JSON.parse(result.text)).toEqual(expectedResponse);
  });

  test('construction/hash - no `0x` prefix', async () => {
    const request: RosettaConstructionHashRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      signed_transaction:
        '0x80800000000400d429e0b599f9cba40ecc9f219df60f9d0a02212d000000000000000100000000000000000101cc0235071690bc762d0013f6d3e4be32aa8f8d01d0db9d845595589edba47e7425bd655f20398e3d931cbe60eea59bb66f44d3f28443078fe9d10082dccef80c010200000000040000000000000000000000000000000000000000000000000000000000000000',
    };

    const result = await supertest(api.server).post(`/rosetta/v1/construction/hash`).send(request);
    expect(result.status).toBe(200);

    const expectedResponse: RosettaConstructionHashResponse = {
      transaction_identifier: {
        hash: '0x592fad4733f3e5c65e7dd9c82ad848191993a80cb3d891b6514bda6e3e7a239e',
      },
    };

    expect(JSON.parse(result.text)).toEqual(expectedResponse);
  });

  test('construction/hash - odd number of hex digits', async () => {
    const request: RosettaConstructionHashRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      signed_transaction:
        '80800000000400d429e0b599f9cba40ecc9f219df60f9d0a02212d000000000000000100000000000000000101cc0235071690bc762d0013f6d3e4be32aa8f8d01d0db9d845595589edba47e7425bd655f20398e3d931cbe60eea59bb66f44d3f28443078fe9d10082dccef80c01020000000004000000000000000000000000000000000000000000000000000000000000000',
    };

    const result = await supertest(api.server).post(`/rosetta/v1/construction/hash`).send(request);
    expect(result.status).toBe(400);

    const expectedResponse = RosettaErrors[RosettaErrorsTypes.invalidTransactionString];

    expect(JSON.parse(result.text)).toEqual(expectedResponse);
  });

  test('construction/hash - unsigned transaction', async () => {
    const request: RosettaConstructionHashRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      //unsigned transaction bytes
      signed_transaction:
        '0x80800000000400539886f96611ba3ba6cef9618f8c78118b37c5be000000000000000000000000000000b400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003020000000000051a1ae3f911d8f1d46d7416bfbe4b593fd41eac19cb000000000007a12000000000000000000000000000000000000000000000000000000000000000000000',
    };

    const result = await supertest(api.server).post(`/rosetta/v1/construction/hash`).send(request);
    expect(result.status).toBe(400);

    const expectedResponse = RosettaErrors[RosettaErrorsTypes.transactionNotSigned];

    expect(JSON.parse(result.text)).toEqual(expectedResponse);
  });

  test('construction/parse - signed', async () => {
    const publicKey = publicKeyToString(
      getPublicKey(createStacksPrivateKey(testnetKeys[0].secretKey))
    );
    const senderAddr = testnetKeys[0].stacksAddress;
    const recipientAddr = 'STDE7Y8HV3RX8VBM2TZVWJTS7ZA1XB0SSC3NEVH0';
    const amount = new BN(1000);
    const fee = new BN(180);
    const options: SignedTokenTransferOptions = {
      recipient: recipientAddr,
      amount: amount,
      fee: fee,
      senderKey: testnetKeys[0].secretKey,
      network: getStacksTestnetNetwork(),
      anchorMode: AnchorMode.Any,
    };
    const testTransaction = await makeSTXTokenTransfer(options);
    const request: RosettaConstructionParseRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      signed: true,
      transaction: bufferToHexPrefixString(testTransaction.serialize()),
    };

    const result = await supertest(api.server).post(`/rosetta/v1/construction/parse`).send(request);
    expect(result.status).toBe(200);
    expect(result.type).toBe('application/json');

    const actual: RosettaConstructionParseResponse = JSON.parse(result.text);
    // test fee operation
    expect(actual.operations[0].account?.address).toEqual(senderAddr);
    expect(actual.operations[0].amount?.value).toEqual('-' + fee.toString());
    // test sender
    expect(actual.operations[1].account?.address).toEqual(senderAddr);
    expect(actual.operations[1].amount?.value).toEqual('-' + amount.toString());
    // test recipient
    expect(actual.operations[2].account?.address).toEqual(recipientAddr);
    expect(actual.operations[2].amount?.value).toEqual(amount.toString());
    // test signer
    expect(actual.account_identifier_signers?.[0].address).toEqual(senderAddr);
  });

  test('construction/parse - unsigned', async () => {
    const publicKey = publicKeyToString(
      getPublicKey(createStacksPrivateKey(testnetKeys[0].secretKey))
    );
    const senderAddr = testnetKeys[0].stacksAddress;
    const recipientAddr = 'STDE7Y8HV3RX8VBM2TZVWJTS7ZA1XB0SSC3NEVH0';
    const amount = new BN(1000);
    const fee = new BN(180);
    const tokenTransferOptions: UnsignedTokenTransferOptions = {
      recipient: recipientAddr,
      amount: amount,
      fee: fee,
      publicKey: publicKey,
      network: getStacksTestnetNetwork(),
      anchorMode: AnchorMode.Any,
    };
    const testTransaction = await makeUnsignedSTXTokenTransfer(tokenTransferOptions);

    const request: RosettaConstructionParseRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      signed: false,
      transaction: bufferToHexPrefixString(testTransaction.serialize()),
    };

    const result = await supertest(api.server).post(`/rosetta/v1/construction/parse`).send(request);
    expect(result.status).toBe(200);
    expect(result.type).toBe('application/json');
    const actual: RosettaConstructionParseResponse = JSON.parse(result.text);
    // test fee operation
    expect(actual.operations[0].account?.address).toEqual(senderAddr);
    expect(actual.operations[0].amount?.value).toEqual('-' + fee.toString());
    // test sender
    expect(actual.operations[1].account?.address).toEqual(senderAddr);
    expect(actual.operations[1].amount?.value).toEqual('-' + amount.toString());
    // test recipient
    expect(actual.operations[2].account?.address).toEqual(recipientAddr);
    expect(actual.operations[2].amount?.value).toEqual(amount.toString());
  });

  test('construction/submit', async () => {
    const txOptions = {
      senderKey: testnetKeys[0].secretKey,
      recipient: standardPrincipalCV(testnetKeys[1].stacksAddress),
      amount: new BigNum(12345),
      network: getStacksTestnetNetwork(),
      memo: 'test memo',
      nonce: new BigNum(0),
      fee: new BigNum(200),
      anchorMode: AnchorMode.Any,
    };

    const transaction = await makeSTXTokenTransfer(txOptions);
    const serializedTx = transaction.serialize().toString('hex');

    const request: RosettaConstructionHashRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      // signed transaction bytes
      signed_transaction: '0x' + serializedTx,
    };
    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/submit`)
      .send(request);

    expect(result.status).toBe(200);
  });

  test('construction/submit - unsigned', async () => {
    const txOptions = {
      recipient: standardPrincipalCV(testnetKeys[1].stacksAddress),
      amount: new BigNum(12345),
      publicKey: publicKeyToString(pubKeyfromPrivKey(testnetKeys[0].secretKey)),
      network: getStacksTestnetNetwork(),
      memo: 'test memo',
      nonce: new BigNum(0),
      fee: new BigNum(200),
      anchorMode: AnchorMode.Any,
    };

    const transaction = await makeUnsignedSTXTokenTransfer(txOptions);
    const serializedTx = transaction.serialize().toString('hex');

    const request: RosettaConstructionHashRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      //unsigned transaction bytes
      signed_transaction: '0x' + serializedTx,
    };
    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/submit`)
      .send(request);
    expect(result.status).toBe(400);
    const expectedResponse = RosettaErrors[RosettaErrorsTypes.invalidTransactionString];

    expect(JSON.parse(result.text)).toMatchObject(expectedResponse);
  });

  test('payloads single sign success', async () => {
    const publicKey = publicKeyToString(pubKeyfromPrivKey(testnetKeys[0].secretKey));
    const sender = testnetKeys[0].stacksAddress;
    const recipient = testnetKeys[1].stacksAddress;
    const fee = '180';

    const request: RosettaConstructionPayloadsRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      operations: [
        {
          operation_identifier: {
            index: 0,
            network_index: 0,
          },
          related_operations: [],
          type: 'fee',
          account: {
            address: sender,
            metadata: {},
          },
          amount: {
            value: '-' + fee,
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
        {
          operation_identifier: {
            index: 1,
            network_index: 0,
          },
          related_operations: [],
          type: 'token_transfer',
          account: {
            address: sender,
            metadata: {},
          },
          amount: {
            value: '-500000',
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
        {
          operation_identifier: {
            index: 2,
            network_index: 0,
          },
          related_operations: [],
          type: 'token_transfer',
          account: {
            address: recipient,
            metadata: {},
          },
          amount: {
            value: '500000',
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
      ],
      metadata: {
        account_sequence: 0,
        memo: 'SAMPLE MEMO',
      },
      public_keys: [
        {
          hex_bytes: publicKey,
          curve_type: 'secp256k1',
        },
      ],
    };

    const tokenTransferOptions: UnsignedTokenTransferOptions = {
      recipient: recipient,
      amount: new BN('500000'),
      fee: new BN(fee),
      publicKey: publicKey,
      network: getStacksNetwork(),
      nonce: new BN(0),
      memo: 'SAMPLE MEMO',
      anchorMode: AnchorMode.Any,
    };

    const transaction = await makeUnsignedSTXTokenTransfer(tokenTransferOptions);
    const unsignedTransaction = transaction.serialize();
    // const hexBytes = digestSha512_256(unsignedTransaction).toString('hex');

    const signer = new TransactionSigner(transaction);

    const prehash = makeSigHashPreSign(signer.sigHash, AuthType.Standard, new BN(fee), new BN(0));

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/payloads`)
      .send(request);

    expect(result.status).toBe(200);
    expect(result.type).toBe('application/json');

    const accountIdentifier: RosettaAccountIdentifier = {
      address: sender,
    };

    const expectedResponse = {
      unsigned_transaction: '0x' + unsignedTransaction.toString('hex'),
      payloads: [
        {
          address: sender,
          account_identifier: accountIdentifier,
          hex_bytes: prehash,
          signature_type: 'ecdsa_recovery',
        },
      ],
    };

    expect(JSON.parse(result.text)).toEqual(expectedResponse);
  });

  test('payloads multi sig', async () => {
    const publicKey1 = publicKeyToString(pubKeyfromPrivKey(testnetKeys[0].secretKey));
    const publicKey2 = publicKeyToString(pubKeyfromPrivKey(testnetKeys[1].secretKey));

    const sender = testnetKeys[0].stacksAddress;
    const recipient = testnetKeys[1].stacksAddress;
    const fee = '180';

    const request: RosettaConstructionPayloadsRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      operations: [
        {
          operation_identifier: {
            index: 0,
            network_index: 0,
          },
          related_operations: [],
          type: 'fee',
          account: {
            address: sender,
            metadata: {},
          },
          amount: {
            value: '-' + fee,
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
        {
          operation_identifier: {
            index: 1,
            network_index: 0,
          },
          related_operations: [],
          type: 'token_transfer',
          account: {
            address: sender,
            metadata: {},
          },
          amount: {
            value: '-500000',
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
        {
          operation_identifier: {
            index: 2,
            network_index: 0,
          },
          related_operations: [],
          type: 'token_transfer',
          account: {
            address: recipient,
            metadata: {},
          },
          amount: {
            value: '500000',
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
      ],
      metadata: {
        fee,
        account_sequence: 0,
      },
      public_keys: [
        {
          hex_bytes: publicKey1,
          curve_type: 'secp256k1',
        },
        {
          hex_bytes: publicKey2,
          curve_type: 'secp256k1',
        },
      ],
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/payloads`)
      .send(request);

    expect(result.status).toBe(400);
    expect(result.type).toBe('application/json');

    const expectedResponse = RosettaErrors[RosettaErrorsTypes.needOnePublicKey];

    expect(JSON.parse(result.text)).toEqual(expectedResponse);
  });

  test('payloads single sign - stacking', async () => {
    const publicKey = publicKeyToString(pubKeyfromPrivKey(testnetKeys[0].secretKey));
    const sender = testnetKeys[0].stacksAddress;
    const fee = '180';
    const contract_address = 'ST000000000000000000002AMW42H';
    const contract_name = 'pox';
    const stacking_amount = 5000;
    const burn_block_height = 200;
    const number_of_cycles = 5;

    const request: RosettaConstructionPayloadsRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      operations: [
        {
          operation_identifier: {
            index: 1,
            network_index: 0,
          },
          related_operations: [],
          type: 'fee',
          account: {
            address: sender,
            metadata: {},
          },
          amount: {
            value: fee,
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata:{
            	number_of_cycles: 5
            },
          },
        },
        {
          operation_identifier: {
            index: 1,
            network_index: 0,
          },
          related_operations: [],
          type: 'stack_stx',
          account: {
            address: sender,
            metadata: {},
          },
          amount: {
            value: '-' + stacking_amount,
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
          },
          metadata: {
            number_of_cycles: number_of_cycles,
            pox_addr : '1Xik14zRm29UsyS6DjhYg4iZeZqsDa8D3',

          }
        },
      ],
      metadata: {
        account_sequence: 0,
        number_of_cycles: number_of_cycles, 
        contract_address: contract_address, 
        contract_name: contract_name,
        burn_block_height: burn_block_height, 

      },
      public_keys: [
        {
          hex_bytes: publicKey,
          curve_type: 'secp256k1',
        },
      ],
    };

    const poxBTCAddress = '1Xik14zRm29UsyS6DjhYg4iZeZqsDa8D3'

    const { hashMode, data } = decodeBtcAddress(poxBTCAddress);
    const hashModeBuffer = bufferCV(new BN(hashMode, 10).toArrayLike(Buffer));
    const hashbytes = bufferCV(data);
    const poxAddressCV = tupleCV({
      hashbytes,
      version: hashModeBuffer,
    });


    const stackingTx: UnsignedContractCallOptions = {
      contractAddress: contract_address,
      contractName: contract_name,
      functionName: 'stack-stx',
      publicKey: publicKey,
      functionArgs: [
        uintCV(stacking_amount),
        poxAddressCV,
        uintCV(burn_block_height),
        uintCV(number_of_cycles),
      ],
      validateWithAbi: false,
      nonce: new BN(0), 
      fee: new BN(fee),
      network: getStacksNetwork(),
      anchorMode: AnchorMode.Any,
    };
    const transaction = await makeUnsignedContractCall(stackingTx);
    const unsignedTransaction = transaction.serialize();
    // const hexBytes = digestSha512_256(unsignedTransaction).toString('hex');

    const signer = new TransactionSigner(transaction);

    const prehash = makeSigHashPreSign(signer.sigHash, AuthType.Standard, new BN(fee), new BN(0));

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/payloads`)
      .send(request);

    expect(result.status).toBe(200);
    expect(result.type).toBe('application/json');

    const accountIdentifier: RosettaAccountIdentifier = {
      address: sender,
    };

    const expectedResponse = {
      unsigned_transaction: '0x' + unsignedTransaction.toString('hex'),
      payloads: [
        {
          address: sender,
          account_identifier: accountIdentifier,
          hex_bytes: prehash,
          signature_type: 'ecdsa_recovery',
        },
      ],
    };

    expect(JSON.parse(result.text)).toEqual(expectedResponse);
  });

  test('payloads public key not added', async () => {
    const request: RosettaConstructionPayloadsRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      operations: [
        {
          operation_identifier: {
            index: 0,
            network_index: 0,
          },
          related_operations: [],
          type: 'fee',
          account: {
            address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
            metadata: {},
          },
          amount: {
            value: '-180',
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
        {
          operation_identifier: {
            index: 1,
            network_index: 0,
          },
          related_operations: [],
          type: 'token_transfer',
          account: {
            address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
            metadata: {},
          },
          amount: {
            value: '-500000',
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
        {
          operation_identifier: {
            index: 2,
            network_index: 0,
          },
          related_operations: [],
          type: 'token_transfer',
          account: {
            address: 'STDE7Y8HV3RX8VBM2TZVWJTS7ZA1XB0SSC3NEVH0',
            metadata: {},
          },
          amount: {
            value: '500000',
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
      ],
      metadata: {
        fee: '180',
        account_sequence: 0,
      },
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/payloads`)
      .send(request);

    expect(result.status).toBe(400);
    expect(result.type).toBe('application/json');

    const expectedResponse = RosettaErrors[RosettaErrorsTypes.emptyPublicKey];

    expect(JSON.parse(result.text)).toEqual(expectedResponse);
  });

  test('payloads public key invalid curve type', async () => {
    const request: RosettaConstructionPayloadsRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      operations: [
        {
          operation_identifier: {
            index: 0,
            network_index: 0,
          },
          related_operations: [],
          type: 'fee',
          account: {
            address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
            metadata: {},
          },
          amount: {
            value: '-180',
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
        {
          operation_identifier: {
            index: 1,
            network_index: 0,
          },
          related_operations: [],
          type: 'token_transfer',
          account: {
            address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
            metadata: {},
          },
          amount: {
            value: '-500000',
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
        {
          operation_identifier: {
            index: 2,
            network_index: 0,
          },
          related_operations: [],
          type: 'token_transfer',
          account: {
            address: 'STDE7Y8HV3RX8VBM2TZVWJTS7ZA1XB0SSC3NEVH0',
            metadata: {},
          },
          amount: {
            value: '500000',
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
        },
      ],
      metadata: {
        account_sequence: 0,
      },
      public_keys: [
        {
          hex_bytes: '025c13b2fc2261956d8a4ad07d481b1a3b2cbf93a24f992249a61c3a1c4de79c51',
          curve_type: 'edwards25519',
        },
      ],
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/payloads`)
      .send(request);

    expect(result.status).toBe(400);
    expect(result.type).toBe('application/json');

    const expectedResponse = RosettaErrors[RosettaErrorsTypes.invalidCurveType];

    expect(JSON.parse(result.text)).toEqual(expectedResponse);
  });

  test('combine single sign success', async () => {
    const publicKey = publicKeyToString(pubKeyfromPrivKey(testnetKeys[0].secretKey));

    const txOptions: UnsignedTokenTransferOptions = {
      publicKey: publicKey,
      recipient: standardPrincipalCV(testnetKeys[1].stacksAddress),
      amount: new BigNum(12345),
      network: getStacksTestnetNetwork(),
      memo: 'test memo',
      nonce: new BigNum(0),
      fee: new BigNum(200),
      anchorMode: AnchorMode.Any,
    };

    const unsignedTransaction = await makeUnsignedSTXTokenTransfer(txOptions);
    const unsignedSerializedTx = unsignedTransaction.serialize().toString('hex');

    const signer = new TransactionSigner(unsignedTransaction);

    const prehash = makeSigHashPreSign(signer.sigHash, AuthType.Standard, new BigNum(200), new BigNum(0));

    signer.signOrigin(createStacksPrivateKey(testnetKeys[0].secretKey));
    const signedSerializedTx = signer.transaction.serialize().toString('hex');

    const signature: MessageSignature = getSignature(signer.transaction) as MessageSignature;

    const request: RosettaConstructionCombineRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      unsigned_transaction: '0x' + unsignedSerializedTx,
      signatures: [
        {
          signing_payload: {
            hex_bytes: prehash,
            signature_type: 'ecdsa_recovery',
          },
          public_key: {
            hex_bytes: publicKey,
            curve_type: 'secp256k1',
          },
          signature_type: 'ecdsa_recovery',
          hex_bytes: signature.data.slice(2) + signature.data.slice(0, 2),
        },
      ],
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/combine`)
      .send(request);

    expect(result.status).toBe(200);
    expect(result.type).toBe('application/json');

    const expectedResponse: RosettaConstructionCombineResponse = {
      signed_transaction: '0x' + signedSerializedTx,
    };

    expect(JSON.parse(result.text)).toEqual(expectedResponse);
  });

  test('combine multi sig', async () => {
    const request: RosettaConstructionCombineRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      unsigned_transaction:
        '00000000010400539886f96611ba3ba6cef9618f8c78118b37c5be0000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003020000000000051ab71a091b4b8b7661a661c620966ab6573bc2dcd3000000000007a12074657374207472616e73616374696f6e000000000000000000000000000000000000',
      signatures: [
        {
          signing_payload: {
            hex_bytes:
              '017c7fc676effda9d905440a052d304b5d9705c30625e654f5b3c9ed461337cdec695d14e5f24091d61f8409f2ab703102ca840dbf5ef752ec534fe1f418552201',
            signature_type: 'ecdsa',
          },
          public_key: {
            hex_bytes: '025c13b2fc2261956d8a4ad07d481b1a3b2cbf93a24f992249a61c3a1c4de79c51',
            curve_type: 'secp256k1',
          },
          signature_type: 'ecdsa',
          hex_bytes:
            '017c7fc676effda9d905440a052d304b5d9705c30625e654f5b3c9ed461337cdec695d14e5f24091d61f8409f2ab703102ca840dbf5ef752ec534fe1f418552201',
        },
        {
          signing_payload: {
            hex_bytes:
              '017c7fc676effda9d905440a052d304b5d9705c30625e654f5b3c9ed461337cdec695d14e5f24091d61f8409f2ab703102ca840dbf5ef752ec534fe1f418552201',
            signature_type: 'ecdsa_recovery',
          },
          public_key: {
            hex_bytes: '025c13b2fc2261956d8a4ad07d481b1a3b2cbf93a24f992249a61c3a1c4de79c51',
            curve_type: 'secp256k1',
          },
          signature_type: 'ecdsa_recovery',
          hex_bytes:
            '017c7fc676effda9d905440a052d304b5d9705c30625e654f5b3c9ed461337cdec695d14e5f24091d61f8409f2ab703102ca840dbf5ef752ec534fe1f418552201',
        },
      ],
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/combine`)
      .send(request);

    expect(result.status).toBe(400);
    expect(result.type).toBe('application/json');

    const expectedResponse = RosettaErrors[RosettaErrorsTypes.needOnlyOneSignature];

    expect(JSON.parse(result.text)).toEqual(expectedResponse);
  });

  test('combine invalid transaction', async () => {
    const request: RosettaConstructionCombineRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      unsigned_transaction: 'invalid transaction',
      signatures: [
        {
          signing_payload: {
            hex_bytes:
              '36212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f01',
            signature_type: 'ecdsa_recovery',
          },
          public_key: {
            hex_bytes: '025c13b2fc2261956d8a4ad07d481b1a3b2cbf93a24f992249a61c3a1c4de79c51',
            curve_type: 'secp256k1',
          },
          signature_type: 'ecdsa_recovery',
          hex_bytes:
            '36212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f01',
        },
      ],
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/combine`)
      .send(request);

    expect(result.status).toBe(400);
    expect(result.type).toBe('application/json');

    const expectedResponse = RosettaErrors[RosettaErrorsTypes.invalidTransactionString];

    expect(JSON.parse(result.text)).toEqual(expectedResponse);
  });

  test('combine invalid signature', async () => {
    const request: RosettaConstructionCombineRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      unsigned_transaction:
        '00000000010400539886f96611ba3ba6cef9618f8c78118b37c5be0000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003020000000000051ab71a091b4b8b7661a661c620966ab6573bc2dcd3000000000007a12074657374207472616e73616374696f6e000000000000000000000000000000000000',
      signatures: [
        {
          signing_payload: {
            hex_bytes: 'invalid signature',
            signature_type: 'ecdsa_recovery',
          },
          public_key: {
            hex_bytes: '025c13b2fc2261956d8a4ad07d481b1a3b2cbf93a24f992249a61c3a1c4de79c51',
            curve_type: 'secp256k1',
          },
          signature_type: 'ecdsa_recovery',
          hex_bytes: 'invalid signature',
        },
      ],
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/combine`)
      .send(request);

    expect(result.status).toBe(400);
    expect(result.type).toBe('application/json');

    const expectedResponse = RosettaErrors[RosettaErrorsTypes.invalidSignature];

    expect(JSON.parse(result.text)).toEqual(expectedResponse);
  });

  test('combine signature not verified', async () => {
    const publicKey = publicKeyToString(pubKeyfromPrivKey(testnetKeys[0].secretKey));

    const txOptions: UnsignedTokenTransferOptions = {
      publicKey: publicKey,
      recipient: standardPrincipalCV(testnetKeys[1].stacksAddress),
      amount: new BigNum(12345),
      network: getStacksTestnetNetwork(),
      memo: 'test memo',
      nonce: new BigNum(0),
      fee: new BigNum(200),
      anchorMode: AnchorMode.Any,
    };

    const unsignedTransaction = await makeUnsignedSTXTokenTransfer(txOptions);
    const unsignedSerializedTx = unsignedTransaction.serialize().toString('hex');

    const signer = new TransactionSigner(unsignedTransaction);
    signer.signOrigin(createStacksPrivateKey(testnetKeys[1].secretKey)); // use different secret key to sign

    const signature = getSignature(unsignedTransaction);
    if (!signature) throw Error('Signature undefined');

    const request: RosettaConstructionCombineRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      unsigned_transaction: unsignedSerializedTx,
      signatures: [
        {
          signing_payload: {
            hex_bytes: signature.data,
            signature_type: 'ecdsa_recovery',
          },
          public_key: {
            hex_bytes: '025c13b2fc2261956d8a4ad07d481b1a3b2cbf93a24f992249a61c3a1c4de79c51',
            curve_type: 'secp256k1',
          },
          signature_type: 'ecdsa_recovery',
          hex_bytes: signature.data,
        },
      ],
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/combine`)
      .send(request);

    expect(result.status).toBe(400);
    expect(result.type).toBe('application/json');

    const expectedResponse = RosettaErrors[RosettaErrorsTypes.signatureNotVerified];

    expect(JSON.parse(result.text)).toEqual(expectedResponse);
  });

  test('combine invalid public key', async () => {
    const request: RosettaConstructionCombineRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      unsigned_transaction:
        '80800000000400539886f96611ba3ba6cef9618f8c78118b37c5be000000000000000000000000000000b4000136212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f03020000000000051ab71a091b4b8b7661a661c620966ab6573bc2dcd3000000000007a12074657374207472616e73616374696f6e000000000000000000000000000000000000',
      signatures: [
        {
          signing_payload: {
            hex_bytes:
              '36212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f01',
            signature_type: 'ecdsa_recovery',
          },
          public_key: {
            hex_bytes: 'invalid  public key',
            curve_type: 'secp256k1',
          },
          signature_type: 'ecdsa_recovery',
          hex_bytes:
            '36212600bf7463399a23c398f29ca7006b9986b4a01129dd7c6e89314607208e516b0b28c1d850fe6e164abea7b6cceb4aa09700a6d218d1b605d4a402d3038f01',
        },
      ],
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/combine`)
      .send(request);

    expect(result.status).toBe(400);
    expect(result.type).toBe('application/json');

    const expectedResponse = RosettaErrors[RosettaErrorsTypes.signatureNotVerified];

    expect(JSON.parse(result.text)).toEqual(expectedResponse);
  });

  test('construction/metadata - stacking', async () => {
    const publicKey = publicKeyToString(
      getPublicKey(createStacksPrivateKey(testnetKeys[0].secretKey))
    );
    const request: RosettaConstructionMetadataRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      options: {
        sender_address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
        type: 'stack_stx',
        suggested_fee_multiplier: 1,
        amount: '-500000',
        symbol: 'STX',
        decimals: 6,
        max_fee: '12380898',
        number_of_cycles: 3,
        size: 260,
      },
      public_keys: [{ hex_bytes: publicKey, curve_type: 'secp256k1' }],
    };

    await standByForPoxToBeReady();

    const result = await supertest(api.server)
    .post(`/rosetta/v1/construction/metadata`)
    .send(request);
    expect(result.status).toBe(200);
    expect(result.type).toBe('application/json');

    expect(JSON.parse(result.text)).toHaveProperty('metadata');
    expect(JSON.parse(result.text)).toHaveProperty('suggested_fee');
    expect(JSON.parse(result.text).metadata).toHaveProperty('contract_address');
    expect(JSON.parse(result.text).metadata).toHaveProperty('contract_name');
    expect(JSON.parse(result.text).metadata).toHaveProperty('burn_block_height');
    expect(JSON.parse(result.text).suggested_fee[0].value).toBe('260');

  });

  test('construction/metadata - delegate_stacking', async () => {
    const publicKey = publicKeyToString(
      getPublicKey(createStacksPrivateKey(testnetKeys[0].secretKey))
    );
    const request: RosettaConstructionMetadataRequest = {
      network_identifier: {
        blockchain: 'stacks',
        network: 'testnet',
      },
      options: {
        fee: '180',
        sender_address: testnetKeys[0].stacksAddress,
        type: 'delegate_stx',
        suggested_fee_multiplier: 1,
        amount: '500000',
        symbol: 'STX',
        decimals: 6,
        max_fee: '12380898',
        delegate_to: testnetKeys[1].stacksAddress,
        size: 260,
      },
      public_keys: [{ hex_bytes: publicKey, curve_type: 'secp256k1' }],
    };

    const result = await supertest(api.server)
      .post(`/rosetta/v1/construction/metadata`)
      .send(request);

    expect(result.status).toBe(200);
    expect(result.type).toBe('application/json');

    const accountInfo = await new StacksCoreRpcClient().getAccount(testnetKeys[0].stacksAddress);
    const nonce = accountInfo.nonce;

    const metadataResponse: RosettaConstructionMetadataResponse = {
      metadata: {
        fee: '180',
        sender_address: testnetKeys[0].stacksAddress,
        type: 'delegate_stx',
        suggested_fee_multiplier: 1,
        amount: '500000',
        symbol: 'STX',
        decimals: 6,
        max_fee: '12380898',
        delegate_to: testnetKeys[1].stacksAddress,
        size: 260,
        contract_address: 'ST000000000000000000002AMW42H',
        contract_name: 'pox',
        account_sequence: nonce,
        recent_block_hash: '0x969e494d5aee0166016836f97bbeb3d9473bea8427e477e9de253f78d3212354'
      },
      suggested_fee: [ { value: '260', currency: {symbol: 'STX', decimals: 6} } ]
    }

    expect(result.body).toHaveProperty('metadata');
    expect(result.body.suggested_fee).toStrictEqual(metadataResponse.suggested_fee);
    expect(result.body.metadata.sender_address).toBe(metadataResponse.metadata.sender_address);
    expect(result.body.metadata.type).toBe(metadataResponse.metadata.type);
    expect(result.body.metadata.suggested_fee_multiplier).toBe(metadataResponse.metadata.suggested_fee_multiplier);
    expect(result.body.metadata.amount).toBe(metadataResponse.metadata.amount);
    expect(result.body.metadata.symbol).toBe(metadataResponse.metadata.symbol);
    expect(result.body.metadata.decimals).toBe(metadataResponse.metadata.decimals);
    expect(result.body.metadata.max_fee).toBe(metadataResponse.metadata.max_fee);
    expect(result.body.metadata.delegate_to).toBe(metadataResponse.metadata.delegate_to);
    expect(result.body.metadata.size).toBe(metadataResponse.metadata.size);
    expect(result.body.metadata.contract_address).toBe(metadataResponse.metadata.contract_address);
    expect(result.body.metadata.contract_name).toBe(metadataResponse.metadata.contract_name);
    expect(result.body.metadata.fee).toBe(metadataResponse.metadata.fee);
    expect(result.body.metadata.nonce).toBe(metadataResponse.metadata.nonce);
    expect(result.body.metadata.recent_block_hash).toBeTruthy();
  });

  test('stacking rosetta transaction cycle', async() => {

    //derive
    const publicKey = publicKeyToString(
      getPublicKey(createStacksPrivateKey(testnetKeys[0].secretKey))
    );
    const deriveRequest: RosettaConstructionDeriveRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      public_key: {
        curve_type: 'secp256k1',
        hex_bytes: publicKey,
      },
    };
    const deriveResult = await supertest(api.server)
      .post(`/rosetta/v1/construction/derive`)
      .send(deriveRequest);

    expect(deriveResult.status).toBe(200);
    expect(deriveResult.type).toBe('application/json');

    const deriveExpectResponse: RosettaConstructionDeriveResponse = {
      account_identifier: { address: testnetKeys[0].stacksAddress },
    };

    expect(deriveResult.body).toEqual(deriveExpectResponse);


    //preprocess
    const fee = '260';
    const stacking_amount = '1250180000000000';//minimum stacking 
    const sender = deriveResult.body.account_identifier.address;
    const number_of_cycles = 3;
    const pox_addr = '2MtzNEqm2D9jcbPJ5mW7Z3AUNwqt3afZH66';
    const size = 260;
    const max_fee = '12380898';
    const preprocessRequest: RosettaConstructionPreprocessRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      operations: [

        {
          operation_identifier: {
            index: 0,
            network_index: 0,
          },
          related_operations: [],
          type: 'stack_stx',
          account: {
            address: sender,
            metadata: {},
          },
          amount: {
            value: '-'+stacking_amount,
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
          metadata: {
            number_of_cycles: number_of_cycles,
            pox_addr: pox_addr,
          },
        },
        {
          operation_identifier: {
            index: 1,
            network_index: 0,
          },
          related_operations: [],
          type: 'fee',
          account: {
            address: sender,
            metadata: {},
          },
          amount: {
            value: fee,
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
          },
        }
      ],
      metadata: {},
      max_fee: [
        {
          value: max_fee,
          currency: {
            symbol: 'STX',
            decimals: 6,
          },
          metadata: {},
        },
      ],
      suggested_fee_multiplier: 1,
    };
    const preprocessResult = await supertest(api.server)
      .post(`/rosetta/v1/construction/preprocess`)
      .send(preprocessRequest);

    expect(preprocessResult.status).toBe(200);
    expect(preprocessResult.type).toBe('application/json');

    const expectResponse: RosettaConstructionPreprocessResponse = {
      options: {
        fee: fee,
        sender_address: sender,
        type: 'stack_stx',
        suggested_fee_multiplier: 1,
        amount: stacking_amount,
        symbol: 'STX',
        decimals: 6,
        max_fee: max_fee,
        size: size,
        number_of_cycles: number_of_cycles,
        pox_addr: pox_addr
      },
      required_public_keys: [
        {
          address: sender,
        },
      ],
    };
    expect(preprocessResult.body).toEqual(expectResponse);

    //metadata 
    const metadataRequest: RosettaConstructionMetadataRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      options: preprocessResult.body.options, //using options returned from preprocess 
      public_keys: [{ hex_bytes: publicKey, curve_type: 'secp256k1' }],
    };
    const resultMetadata = await supertest(api.server)
      .post(`/rosetta/v1/construction/metadata`)
      .send(metadataRequest);
    expect(resultMetadata.status).toBe(200);
    expect(resultMetadata.type).toBe('application/json');
    expect(JSON.parse(resultMetadata.text)).toHaveProperty('metadata');
    expect(JSON.parse(resultMetadata.text)).toHaveProperty('suggested_fee');
    expect(JSON.parse(resultMetadata.text).metadata).toHaveProperty('contract_address');
    expect(JSON.parse(resultMetadata.text).metadata).toHaveProperty('contract_name');
    expect(JSON.parse(resultMetadata.text).metadata).toHaveProperty('burn_block_height');
    expect(JSON.parse(resultMetadata.text).suggested_fee[0].value).toBe('260');

    //payloads
    const contract_address = resultMetadata.body.metadata.contract_address;
    const contract_name = resultMetadata.body.metadata.contract_name;
    const burn_block_height = resultMetadata.body.metadata.burn_block_height;
    const nonce = resultMetadata.body.metadata.account_sequence;
    const payloadsRequest: RosettaConstructionPayloadsRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      operations: preprocessRequest.operations, //using operations same as preprocess request
      metadata: resultMetadata.body.metadata, //using metadata from metadata response
      public_keys: [
        {
          hex_bytes: publicKey,
          curve_type: 'secp256k1',
        },
      ],
    };
    const { hashMode, data } = decodeBtcAddress(pox_addr);
    const hashModeBuffer = bufferCV(new BN(hashMode, 10).toArrayLike(Buffer));
    const hashbytes = bufferCV(data);
    const poxAddressCV = tupleCV({
      hashbytes,
      version: hashModeBuffer,
    });
    const stackingTx: UnsignedContractCallOptions = {
      contractAddress: contract_address,
      contractName: contract_name,
      functionName: 'stack-stx',
      publicKey: publicKey,
      functionArgs: [
        uintCV(stacking_amount),
        poxAddressCV,
        uintCV(burn_block_height),
        uintCV(number_of_cycles),
      ],
      validateWithAbi: false,
      nonce: new BN(nonce), 
      fee: new BN(fee),
      network: getStacksNetwork(),
      anchorMode: AnchorMode.Any,
    };
    const transaction = await makeUnsignedContractCall(stackingTx);
    const unsignedTransaction = transaction.serialize();
    const signer = new TransactionSigner(transaction);
    const prehash = makeSigHashPreSign(signer.sigHash, AuthType.Standard, new BN(fee), new BN(nonce));
    const payloadsResult = await supertest(api.server)
      .post(`/rosetta/v1/construction/payloads`)
      .send(payloadsRequest);
    expect(payloadsResult.status).toBe(200);
    expect(payloadsResult.type).toBe('application/json');
    const accountIdentifier: RosettaAccountIdentifier = {
      address: sender,
    };
    const payloadsExpectedResponse = {
      unsigned_transaction: '0x' + unsignedTransaction.toString('hex'),
      payloads: [
        {
          address: sender,
          account_identifier: accountIdentifier,
          hex_bytes: prehash,
          signature_type: 'ecdsa_recovery',
        },
      ],
    };
    expect(JSON.parse(payloadsResult.text)).toEqual(payloadsExpectedResponse);

    //combine
    signer.signOrigin(createStacksPrivateKey(testnetKeys[0].secretKey));
    const signedSerializedTx = signer.transaction.serialize().toString('hex');
    const signature: MessageSignature = getSignature(signer.transaction) as MessageSignature;
    const combineRequest: RosettaConstructionCombineRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      unsigned_transaction: payloadsExpectedResponse.unsigned_transaction,
      signatures: [
        {
          signing_payload: {
            hex_bytes: prehash,
            signature_type: 'ecdsa_recovery',
          },
          public_key: {
            hex_bytes: publicKey,
            curve_type: 'secp256k1',
          },
          signature_type: 'ecdsa_recovery',
          hex_bytes: signature.data.slice(2) + signature.data.slice(0, 2),
        },
      ],
    };
    const combineResult = await supertest(api.server)
      .post(`/rosetta/v1/construction/combine`)
      .send(combineRequest);
    expect(combineResult.status).toBe(200);
    expect(combineResult.type).toBe('application/json');
    const combineExpectedResponse: RosettaConstructionCombineResponse = {
      signed_transaction: '0x' + signedSerializedTx,
    };
    expect(combineResult.body).toEqual(combineExpectedResponse);

    //hash
    const hashRequest: RosettaConstructionHashRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      signed_transaction: combineExpectedResponse.signed_transaction,
    };

    const hashResult = await supertest(api.server).post(`/rosetta/v1/construction/hash`).send(hashRequest);
    expect(hashResult.status).toBe(200);

    const hashExpectedResponse: RosettaConstructionHashResponse = {
      transaction_identifier: {
        hash: '0x' + transaction.txid(),
      },
    };

    expect(JSON.parse(hashResult.text)).toEqual(hashExpectedResponse);

    //submit
    const submitRequest: RosettaConstructionHashRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      // signed transaction bytes
      signed_transaction: combineResult.body.signed_transaction,
    };
    const submitResult = await supertest(api.server)
      .post(`/rosetta/v1/construction/submit`)
      .send(submitRequest);
    expect(submitResult.status).toBe(200);
    expect(submitResult.body.transaction_identifier.hash).toBe(hashExpectedResponse.transaction_identifier.hash);



    //rosetta block
    const stxLockedTransaction = await standByForTx(submitResult.body.transaction_identifier.hash);

    const blockHeight = stxLockedTransaction.block_height;
    let block = await api.datastore.getBlock({ height: blockHeight });
    assert(block.found);
    const txs = await api.datastore.getBlockTxsRows(block.result.block_hash);
    assert(txs.found);

    const blockStxOpsQuery = await supertest(api.address)
      .post(`/rosetta/v1/block`)
      .send({
        network_identifier: { blockchain: 'stacks', network: 'testnet' },
        block_identifier: { hash: block.result.block_hash },
      });
    expect(blockStxOpsQuery.status).toBe(200);
    expect(blockStxOpsQuery.type).toBe('application/json');
    
    let stxUnlockHeight = Number.parseInt(blockStxOpsQuery.body.block.transactions[1].operations[1].metadata.unlock_burn_height);
    expect(stxUnlockHeight).toBeTruthy();

    const blockTxOpsQuery = await supertest(api.address)
      .post('/rosetta/v1/block/transaction')
      .send({
        network_identifier: { blockchain: 'stacks', network: 'testnet' },
        block_identifier: { hash: block.result.block_hash },
        transaction_identifier: { hash: stxLockedTransaction.tx_id },
      });
    expect(blockTxOpsQuery.status).toBe(200);
    expect(blockTxOpsQuery.type).toBe('application/json');

    const expectedStackStxOp = {
      type: 'stack_stx',
      account: {
        address: testnetKeys[0].stacksAddress,
      },
      metadata: {
        lock_period: number_of_cycles.toString(),
        amount_ustx: stacking_amount,
        stacker_address: testnetKeys[0].stacksAddress,
        pox_addr: pox_addr,
        start_burn_height: burn_block_height.toString(),
        unlock_burn_height: stxUnlockHeight.toString(),
      },
    };

    const expectedStxLockOp = {
      type: 'stx_lock',
      account: {
        address: testnetKeys[0].stacksAddress,
      },
      metadata: {
        locked: stacking_amount,
        unlock_height: stxUnlockHeight.toString(),
      },
      amount: {
        value: '-' + stacking_amount,
        currency: {
          decimals: 6,
          symbol: "STX",
        },
      },
    };

    expect(blockStxOpsQuery.body.block.transactions[1].operations).toContainEqual(expect.objectContaining(expectedStackStxOp));
    expect(blockStxOpsQuery.body.block.transactions[1].operations).toContainEqual(expect.objectContaining(expectedStxLockOp));

    expect(blockTxOpsQuery.body.operations).toContainEqual(expect.objectContaining(expectedStackStxOp));
    expect(blockTxOpsQuery.body.operations).toContainEqual(expect.objectContaining(expectedStxLockOp));

    let current_burn_block_height = block.result.burn_block_height;
    //wait for the unlock block height
    while(current_burn_block_height < stxUnlockHeight){
      block = await db.getCurrentBlock();
      assert(block.found);
      current_burn_block_height =  block.result?.burn_block_height;
      await timeout(100);
    }

    const query1 = await supertest(api.address)
      .post(`/rosetta/v1/block`)
      .send({
        network_identifier: { blockchain: 'stacks', network: 'testnet' },
        block_identifier: { hash: block.result.block_hash },
      });

    expect(query1.status).toBe(200);
    expect(query1.type).toBe('application/json');
    expect(query1.body.block.transactions[0].operations[0].type).toBe('coinbase');
    expect(query1.body.block.transactions[0].operations[1].type).toBe('stx_unlock');

    const query2 = await supertest(api.address)
    .post(`/rosetta/v1/block/transaction`)
    .send({
      network_identifier: { blockchain: 'stacks', network: 'testnet' },
      block_identifier: { hash: block.result.block_hash },
      transaction_identifier: {hash: query1.body.block.transactions[0].transaction_identifier.hash }
    });

    const expectedResponse = {
      operation_identifier: {
        index: 1,
      },
      type: "stx_unlock",
      status: "success",
      account: {
        address: testnetKeys[0].stacksAddress,
      },
      amount: {
        value: stacking_amount,
        currency: {
          decimals: 6,
          symbol: "STX"
        }
      },
      metadata: {
        tx_id: query1.body.block.transactions[0].transaction_identifier.hash,
      }
    }
    expect(query2.status).toBe(200);
    expect(query2.type).toBe('application/json');
    expect(query2.body.operations[1]).toStrictEqual(expectedResponse);

  })

  test('delegate-stacking rosetta transaction cycle', async() => {

    //derive
    const publicKey = publicKeyToString(
      getPublicKey(createStacksPrivateKey(testnetKeys[1].secretKey))
    );
    const deriveRequest: RosettaConstructionDeriveRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      public_key: {
        curve_type: 'secp256k1',
        hex_bytes: publicKey,
      },
    };
    const deriveResult = await supertest(api.server)
      .post(`/rosetta/v1/construction/derive`)
      .send(deriveRequest);

    expect(deriveResult.status).toBe(200);
    expect(deriveResult.type).toBe('application/json');

    const deriveExpectResponse: RosettaConstructionDeriveResponse = {
      account_identifier: { address: testnetKeys[1].stacksAddress },
    };

    expect(deriveResult.body).toEqual(deriveExpectResponse);


    //preprocess
    const fee = '260';
    const stacking_amount = '1250180000000000';//minimum stacking 
    const sender = deriveResult.body.account_identifier.address;
    const pox_addr = '2MtzNEqm2D9jcbPJ5mW7Z3AUNwqt3afZH66';
    const clarityPoxAddr = '0x0c000000020968617368627974657302000000141320e6542e2146ea486700f4091aa793e73607880776657273696f6e020000000101';
    const size = 253;
    const max_fee = '12380898';
    const delegate_to = testnetKeys[2].stacksAddress;

    const preprocessRequest: RosettaConstructionPreprocessRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      operations: [

        {
          operation_identifier: {
            index: 0,
            network_index: 0,
          },
          related_operations: [],
          type: 'delegate_stx',
          account: {
            address: sender,
            metadata: {},
          },
          amount: {
            value: '-'+stacking_amount,
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
            metadata: {},
          },
          metadata: {
            pox_addr: pox_addr,
            delegate_to: delegate_to
          },
        },
        {
          operation_identifier: {
            index: 1,
            network_index: 0,
          },
          related_operations: [],
          type: 'fee',
          account: {
            address: sender,
            metadata: {},
          },
          amount: {
            value: fee,
            currency: {
              symbol: 'STX',
              decimals: 6,
            },
          },
        }
      ],
      metadata: {},
      max_fee: [
        {
          value: max_fee,
          currency: {
            symbol: 'STX',
            decimals: 6,
          },
          metadata: {},
        },
      ],
      suggested_fee_multiplier: 1,
    };

    const preprocessResult = await supertest(api.server)
      .post(`/rosetta/v1/construction/preprocess`)
      .send(preprocessRequest);

    expect(preprocessResult.status).toBe(200);
    expect(preprocessResult.type).toBe('application/json');

    const expectResponse: RosettaConstructionPreprocessResponse = {
      options: {
        fee: fee,
        sender_address: sender,
        type: 'delegate_stx',
        suggested_fee_multiplier: 1,
        amount: stacking_amount,
        symbol: 'STX',
        decimals: 6,
        max_fee: max_fee,
        size: size,
        pox_addr: pox_addr, 
        delegate_to: testnetKeys[2].stacksAddress
      },
      required_public_keys: [
        {
          address: sender,
        },
      ],
    };
    expect(preprocessResult.body).toEqual(expectResponse);

    // //metadata 

    const contract_address = 'ST000000000000000000002AMW42H';
    const contract_name = 'pox';

    const metadataRequest: RosettaConstructionMetadataRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      options: preprocessResult.body.options, //using options returned from preprocess 
      public_keys: [{ hex_bytes: publicKey, curve_type: 'secp256k1' }],
    };

    await standByForPoxToBeReady();

    const resultMetadata = await supertest(api.server)
      .post(`/rosetta/v1/construction/metadata`)
      .send(metadataRequest);

      const accountInfo = await new StacksCoreRpcClient().getAccount(sender);
      const nonce = accountInfo.nonce;

      const metadataResponse: RosettaConstructionMetadataResponse = {
        metadata: {
          fee: fee,
          sender_address: sender,
          type: 'delegate_stx',
          suggested_fee_multiplier: 1,
          amount: stacking_amount,
          symbol: 'STX',
          decimals: 6,
          max_fee: max_fee,
          delegate_to: testnetKeys[2].stacksAddress,
          size: size,
          contract_address: contract_address,
          contract_name: contract_name,
          account_sequence: nonce,
          recent_block_hash: '0x969e494d5aee0166016836f97bbeb3d9473bea8427e477e9de253f78d3212354'
        },
        suggested_fee: [ { value: size.toString(), currency: {symbol: 'STX', decimals: 6} } ]
      }
      expect(resultMetadata.body).toHaveProperty('metadata');
      expect(resultMetadata.body.suggested_fee).toStrictEqual(metadataResponse.suggested_fee);
      expect(resultMetadata.body.metadata.sender_address).toBe(metadataResponse.metadata.sender_address);
      expect(resultMetadata.body.metadata.type).toBe(metadataResponse.metadata.type);
      expect(resultMetadata.body.metadata.suggested_fee_multiplier).toBe(metadataResponse.metadata.suggested_fee_multiplier);
      expect(resultMetadata.body.metadata.amount).toBe(metadataResponse.metadata.amount);
      expect(resultMetadata.body.metadata.symbol).toBe(metadataResponse.metadata.symbol);
      expect(resultMetadata.body.metadata.decimals).toBe(metadataResponse.metadata.decimals);
      expect(resultMetadata.body.metadata.max_fee).toBe(metadataResponse.metadata.max_fee);
      expect(resultMetadata.body.metadata.delegate_to).toBe(metadataResponse.metadata.delegate_to);
      expect(resultMetadata.body.metadata.size).toBe(metadataResponse.metadata.size);
      expect(resultMetadata.body.metadata.contract_address).toBe(metadataResponse.metadata.contract_address);
      expect(resultMetadata.body.metadata.contract_name).toBe(metadataResponse.metadata.contract_name);
      expect(resultMetadata.body.metadata.fee).toBe(metadataResponse.metadata.fee);
      expect(resultMetadata.body.metadata.nonce).toBe(metadataResponse.metadata.nonce);
      expect(resultMetadata.body.metadata.recent_block_hash).toBeTruthy();//can not predict recent block hash

    //payloads
    const payloadsRequest: RosettaConstructionPayloadsRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      operations: preprocessRequest.operations, //using operations same as preprocess request
      metadata: resultMetadata.body.metadata, //using metadata from metadata response
      public_keys: [
        {
          hex_bytes: publicKey,
          curve_type: 'secp256k1',
        },
      ],
    };
    const { hashMode, data } = decodeBtcAddress(pox_addr);
    const hashModeBuffer = bufferCV(new BN(hashMode, 10).toArrayLike(Buffer));
    const hashbytes = bufferCV(data);
    const poxAddressCV = tupleCV({
      hashbytes,
      version: hashModeBuffer,
    });

    const stackingTx: UnsignedContractCallOptions = {
      contractAddress: contract_address,
      contractName: contract_name,
      functionName: 'delegate-stx',
      publicKey: publicKey,
      functionArgs: [
        uintCV(stacking_amount),
        standardPrincipalCV(testnetKeys[2].stacksAddress),
        noneCV(),
        someCV(poxAddressCV),
      ],
      fee: new BN(fee),
      nonce: nonce,
      validateWithAbi: false,
      network: getStacksNetwork(),
      anchorMode: AnchorMode.Any,
    };

    const transaction = await makeUnsignedContractCall(stackingTx);
    const unsignedTransaction = transaction.serialize();
    const signer = new TransactionSigner(transaction);
    const prehash = makeSigHashPreSign(signer.sigHash, AuthType.Standard, new BN(fee), new BN(nonce));
    const payloadsResult = await supertest(api.server)
      .post(`/rosetta/v1/construction/payloads`)
      .send(payloadsRequest);
    expect(payloadsResult.status).toBe(200);
    expect(payloadsResult.type).toBe('application/json');
    const accountIdentifier: RosettaAccountIdentifier = {
      address: sender,
    };
    const payloadsExpectedResponse = {
      unsigned_transaction: '0x' + unsignedTransaction.toString('hex'),
      payloads: [
        {
          address: sender,
          account_identifier: accountIdentifier,
          hex_bytes: prehash,
          signature_type: 'ecdsa_recovery',
        },
      ],
    };
    expect(JSON.parse(payloadsResult.text)).toEqual(payloadsExpectedResponse);

    //combine
    signer.signOrigin(createStacksPrivateKey(testnetKeys[1].secretKey));
    const signedSerializedTx = signer.transaction.serialize().toString('hex');
    const signature: MessageSignature = getSignature(signer.transaction) as MessageSignature;
    const combineRequest: RosettaConstructionCombineRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      unsigned_transaction: payloadsExpectedResponse.unsigned_transaction,
      signatures: [
        {
          signing_payload: {
            hex_bytes: prehash,
            signature_type: 'ecdsa_recovery',
          },
          public_key: {
            hex_bytes: publicKey,
            curve_type: 'secp256k1',
          },
          signature_type: 'ecdsa_recovery',
          hex_bytes: signature.data.slice(2) + signature.data.slice(0, 2),
        },
      ],
    };
    const combineResult = await supertest(api.server)
      .post(`/rosetta/v1/construction/combine`)
      .send(combineRequest);
    expect(combineResult.status).toBe(200);
    expect(combineResult.type).toBe('application/json');
    const combineExpectedResponse: RosettaConstructionCombineResponse = {
      signed_transaction: '0x' + signedSerializedTx,
    };
    expect(combineResult.body).toEqual(combineExpectedResponse);

    // //hash
    const hashRequest: RosettaConstructionHashRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      signed_transaction: combineExpectedResponse.signed_transaction,
    };

    const hashResult = await supertest(api.server).post(`/rosetta/v1/construction/hash`).send(hashRequest);
    expect(hashResult.status).toBe(200);

    const hashExpectedResponse: RosettaConstructionHashResponse = {
      transaction_identifier: {
        hash: '0x' + transaction.txid(),
      },
    };

    expect(JSON.parse(hashResult.text)).toEqual(hashExpectedResponse);

    //submit
    const submitRequest: RosettaConstructionHashRequest = {
      network_identifier: {
        blockchain: RosettaConstants.blockchain,
        network: getRosettaNetworkName(ChainID.Testnet),
      },
      // signed transaction bytes
      signed_transaction: combineResult.body.signed_transaction,
    };
    const submitResult = await supertest(api.server)
      .post(`/rosetta/v1/construction/submit`)
      .send(submitRequest);

    expect(submitResult.status).toBe(200);
    expect(submitResult.body.transaction_identifier.hash).toBe(hashExpectedResponse.transaction_identifier.hash);



    // //rosetta block
    const delegateStx = await standByForTx(submitResult.body.transaction_identifier.hash);

    const blockHeight = delegateStx.block_height;
    let block = await api.datastore.getBlock({ height: blockHeight });
    assert(block.found);
    const txs = await api.datastore.getBlockTxsRows(block.result.block_hash);
    assert(txs.found);

    const blockStxOpsQuery = await supertest(api.address)
      .post(`/rosetta/v1/block`)
      .send({
        network_identifier: { blockchain: 'stacks', network: 'testnet' },
        block_identifier: { hash: block.result.block_hash },
      });
    expect(blockStxOpsQuery.status).toBe(200);
    expect(blockStxOpsQuery.type).toBe('application/json');
    expect(blockStxOpsQuery.body.block.transactions[1].operations[1]).toMatchObject(
      {
        type: 'delegate_stx',
        account: {
          address: testnetKeys[1].stacksAddress,
        },
        metadata: {
          amount_ustx: stacking_amount,
          pox_addr: clarityPoxAddr,
          delegate_to: delegate_to,
        },
      },
    );
  })

  /* rosetta construction end */

  afterAll(async () => {
    await new Promise<void>(resolve => eventServer.close(() => resolve()));
    await api.terminate();
    client.release();
    await db?.close();
    await runMigrations(undefined, 'down');
  });
});
Example #15
Source File: validate-rosetta.ts    From stacks-blockchain-api with GNU General Public License v3.0 4 votes vote down vote up
describe('Rosetta API', () => {
  let db: PgDataStore;
  let client: PoolClient;
  let eventServer: Server;
  let api: ApiServer;
  let rosettaOutput: any;

  beforeAll(async () => {
    process.env.PG_DATABASE = 'postgres';
    await cycleMigrations();
    db = await PgDataStore.connect({ usageName: 'tests' });
    client = await db.pool.connect();
    eventServer = await startEventServer({ datastore: db, chainId: ChainID.Testnet });
    api = await startApiServer({ datastore: db, chainId: ChainID.Testnet });

    // build rosetta-cli container
    await compose.buildOne('rosetta-cli', {
      cwd: path.join(__dirname, '../../'),
      log: true,
      composeOptions: [
        '-f',
        'docker/docker-compose.dev.rosetta-cli.yml',
        '--env-file',
        'src/tests-rosetta-cli-data/envs/env.data',
      ],
    });
    // start cli container
    void compose.upOne('rosetta-cli', {
      cwd: path.join(__dirname, '../../'),
      log: true,
      composeOptions: [
        '-f',
        'docker/docker-compose.dev.rosetta-cli.yml',
        '--env-file',
        'src/tests-rosetta-cli-data/envs/env.data',
      ],
      commandOptions: ['--abort-on-container-exit'],
    });

    await waitForBlock(api);

    for (const addr of recipients) {
      await transferStx(addr, 1000, sender1.privateKey, api);
      await transferStx(addr, 1000, sender2.privateKey, api);
      await transferStx(sender3.address, 6000, sender1.privateKey, api);
    }

    for (const sender of senders) {
      const response = await deployContract(
        sender.privateKey,
        'src/tests-rosetta-cli-data/contracts/hello-world.clar',
        api
      );
      contracts.push(response.contractId);
    }

    for (const contract of contracts) {
      await callContractFunction(api, sender1.privateKey, contract, 'say-hi');
      await callContractFunction(api, sender2.privateKey, contract, 'say-hi');
    }

    // Wait on rosetta-cli to finish output
    while (!rosettaOutput) {
      if (fs.existsSync('docker/rosetta-output/rosetta-cli-output.json')) {
        rosettaOutput = require('../../docker/rosetta-output/rosetta-cli-output.json');
      } else {
        await timeout(1000);
      }
    }
  });

  it('check request/response', () => {
    return expect(rosettaOutput.tests.request_response).toBeTruthy();
  });

  it('check all responses are correct', () => {
    return expect(rosettaOutput.tests.response_assertion).toBeTruthy();
  });

  it('check blocks are connected', () => {
    return expect(rosettaOutput.tests.block_syncing).toBeTruthy();
  });

  it('check negative account balance', () => {
    return expect(rosettaOutput.tests.balance_tracking).toBeTruthy();
  });

  it('check reconciliation', () => {
    return expect(rosettaOutput.tests.reconciliation).toBeTruthy();
  });

  afterAll(async () => {
    await new Promise<void>(resolve => eventServer.close(() => resolve()));
    await api.terminate();
    client.release();
    await db?.close();
    await runMigrations(undefined, 'down');
  });
});
Example #16
Source File: createServer.ts    From web with MIT License 4 votes vote down vote up
/**
 * Creates a koa server with middlewares, but does not start it. Returns the koa app and
 * http server instances.
 */
export function createServer(logger: Logger, cfg: DevServerCoreConfig, fileWatcher: FSWatcher) {
  const app = new Koa();
  app.silent = true;
  app.on('error', error => {
    if (['EPIPE', 'ECONNRESET', 'ERR_STREAM_PREMATURE_CLOSE'].includes(error.code)) {
      return;
    }

    console.error('Error while handling server request.');
    console.error(error);
  });
  addPlugins(logger, cfg);

  // special case the legacy plugin, if it is given make sure the resolve module imports plugin
  // runs before the legacy plugin because it compiles away module syntax. ideally we have a
  // generic API for this, but we need to design that a bit more first
  const indexOfLegacy = cfg.plugins!.findIndex(p => p.name === 'legacy');
  let indexOfResolve = cfg.plugins!.findIndex(p => p.name === 'resolve-module-imports');
  if (indexOfLegacy !== -1 && indexOfResolve !== -1) {
    const legacy = cfg.plugins!.splice(indexOfLegacy, 1)[0];
    // recompute after splicing
    indexOfResolve = cfg.plugins!.findIndex(p => p.name === 'resolve-module-imports');
    cfg.plugins!.splice(indexOfResolve, 1, cfg.plugins![indexOfResolve], legacy);
  }

  const middleware = createMiddleware(cfg, logger, fileWatcher);
  for (const m of middleware) {
    app.use(m);
  }

  let server: Server;
  if (cfg.http2) {
    const dir = path.join(__dirname, '..');
    const options = {
      key: fs.readFileSync(
        cfg.sslKey
          ? path.resolve(cfg.sslKey)
          : path.join(dir, '..', '.self-signed-dev-server-ssl.key'),
      ),
      cert: fs.readFileSync(
        cfg.sslCert
          ? path.resolve(cfg.sslCert)
          : path.join(dir, '..', '.self-signed-dev-server-ssl.cert'),
      ),
      allowHTTP1: true,
    };

    const httpsRedirectServer = httpServer.createServer(httpsRedirect);
    server = http2Server.createSecureServer(options, app.callback());
    let appServerPort: number;
    let httpsRedirectServerPort: number;

    /**
     * A connection handler that checks if the connection is using TLS
     */
    const httpRedirectProxy = (socket: Socket) => {
      socket.once('data', buffer => {
        // A TLS handshake record starts with byte 22.
        const address = buffer[0] === 22 ? appServerPort : httpsRedirectServerPort;
        const proxy = (net as any).createConnection(address, () => {
          proxy.write(buffer);
          socket.pipe(proxy).pipe(socket);
        });
      });
    };

    const wrapperServer = net.createServer(httpRedirectProxy);

    wrapperServer.addListener('close', () => {
      httpsRedirectServer.close();
      server.close();
    });

    wrapperServer.addListener('listening', () => {
      const info = server.address();
      if (!info || typeof info === 'string') {
        return;
      }
      const { address, port } = info;
      appServerPort = port + 1;
      httpsRedirectServerPort = port + 2;

      server.listen({ address, port: appServerPort });
      httpsRedirectServer.listen({ address, port: httpsRedirectServerPort });
    });

    const serverListen = wrapperServer.listen.bind(wrapperServer);
    (wrapperServer as any).listen = (config: ListenOptions, callback: () => void) => {
      server.addListener('listening', callback);
      serverListen(config);
      return server;
    };
  } else {
    server = httpServer.createServer(app.callback());
  }

  return {
    server,
    app,
  };
}