deploy

Stores a Javascript module on the blockchain.

Type

;(module: string) => Promise<string>

Parameters

module

A string encoding a JavaScript module.

Return Value

A string of the form <transaction-id>:<output number> encoding the location where the module is stored.

Description

The deploy function stores a JavaScript module on the blockchain and returns a module specifier that identifies that module. This specifier can be passed into computer.new, computer.encode, computer.encodeNew, and computer.encodeCall as an optional parameter. In this case, the names exported from the module are available in the evaluation of these functions.

Module deploys are not smart-object transitions. They do not store { exp, env, mod, v } metadata. Use computer.load to retrieve and evaluate a module; computer.decode rejects module deploy transactions.

The rationale for deploying modules is to minimize on-chain data (and thereby the technical on-chain storage costs / hygiene dust): a module can be deployed once (preferably via taproot) and then referenced to create or update many smart objects using tiny expressions. This keeps using transactions small. This is unlike on-chain objects whose associated data is tied to a single use/revision. See the Fees "User Choices..." section for full minimization guidance.

On-chain encoding

The protocol supports two module storage types:

  • multisig — One transaction. An owner output is created (so the UTXO appears in the node’s Output table), and the module source is stored in cleartext data outputs as:

    {
      ept: string
    } // module source only; no encryption

    These data outputs use bare multisig scripts. The maximum module size is about 18 kB; larger modules must be split into chunks of less than 18 kB and recombined in another module. Module encoding transactions using multisig generate hygiene dust outputs (see Fees for scope and costs).

  • taproot — Two transactions (commit + reveal). The commit tx locks a taproot output whose leaf commits to the data; the reveal tx spends that output and embeds the full module source in the input witness, inside a script-path envelope tagged with protocol id BC. Content type is text/javascript. These modules can be close to 400 kB or even close to 4 MB (if you know a miner that will include them). Module storage is 4× cheaper due to the SegWit discount. Crucially, taproot module encoding transactions generate no hygiene dust outputs.

Encryption of module payloads is not supported yet (may be added later). Only new-format deploys ({ ept } / BC) are valid; legacy shapes that stored modules as transition exp fields or non-BC inscription envelopes are not read.

To select the module storage type, pass either multisig or taproot into the property moduleStorageType of the Computer constructor. If moduleStorageType is not specified, it will use taproot if available and multisig otherwise. For minimization of on-chain data and hygiene dust (and the UX trade-offs of single-tx vs. two-tx), see the Fees documentation, especially "User Choices to Control On-Chain Data and Hygiene Dust Costs" and the bare-multisig rationale (single-tx direct data for high-throughput reliability).

See also Transaction.onChainMetaData for how metadata differs between transitions and module deploys.

When connected to a Bitcoin Computer Node, deploys are indexed into the node’s Module table. List and fetch them from the client with getModules / getModule, or via the node HTTP API (modules, module).

Coin Taproot Module Storage Types Default Max Module Size Segwit Discount
BTC Yes taproot multisig taproot 4 MB Yes
LTC Yes taproot multisig taproot 4 MB Yes
PEPE No multisig multisig 18 kB No
BCH* No multisig multisig 18 kB No
DOGE* No multisig multisig 18 kB No

* Bitcoin Computer support coming soon

Examples

import { Computer } from '@bitcoin-computer/lib'
import { chain, expect, network, url } from '../../utils/index.js'

describe('deploy', () => {
  let computer: Computer

  before('Create and fund wallet', async () => {
    computer = new Computer({ chain, network, url })
    await computer.faucet(1e8)
  })

  // Modules stored in multisig scripts
  it('Should deploy a module using multisig', async () => {
    const multisigComputer = new Computer({
      chain,
      network,
      url,
      moduleStorageType: 'multisig',
      satPerByte: 4,
    })
    await multisigComputer.faucet(1881764) // 1.9M sats
    const big = `x`.repeat(12155) // ~ 12KB ==>> Each stored byte costs 157 satoshis aprox

    const rev = await multisigComputer.deploy(big)
    expect(rev).to.not.equal(undefined)
  })

  // Modules stored in taproot scripts
  it('Should deploy a module using taproot', async () => {
    if (chain && ['BTC', 'LTC'].includes(chain)) {
      const taprootComputer = new Computer({ chain, network, url, moduleStorageType: 'taproot' })
      await taprootComputer.faucet(436000)
      const veryBig = `x`.repeat(396000) // ~ 400KB

      const rev = await taprootComputer.deploy(veryBig)
      expect(rev).to.not.equal(undefined)
    }
  })

  // Modules that depends on one another
  it('Should deploy a module that depends on another module', async () => {
    const modSpecA = await computer.deploy(`export class A extends Contract {}`)

    const modSpecB = await computer.deploy(`
      import { A } from '${modSpecA}'
      export class B extends A {}
    `)
    const { tx } = await computer.encode({ exp: `new B()`, mod: modSpecB })
    expect(tx!.getId()).to.be.a.string
  })

  // Modules of arbitrary size
  it('Should deploy a module of arbitrary length', async () => {
    // Inputs a long string and outputs a module specifier where it can be obtained
    const store = async (s: string, n: number) => {
      // Partition the long string into chunks of size n
      const chunks = []
      for (let i = 0; i < s.length; i += n) {
        chunks.push(s.slice(i, i + n))
      }

      // Deploy chunks and build recombining module
      let module = ''
      for (let i = 0; i < s.length / n; i += 1) {
        // Deploy a chunk
        const mod = await computer.deploy(`export const c${i} = '${chunks[i]}'`)

        // Import chunk into recombining module
        module += `import { c${i} } from '${mod}'\n`
      }

      // Export concatenation of chunks
      const cs = Array.from({ length: n + 1 }, (_, i) => `c${i}`).join(' + ')
      module += `export const s = ${cs}`

      // Deploy recombining module
      return computer.deploy(module)
    }

    // Create a long string, could be hundreds MB in a real example
    const longString = '0'.repeat(10)

    // Store long string, you can use 18262 instead of 3 for multisig modules
    // or 396000 for taproot modules
    const mod = await store(longString, 3)

    // Load a long string
    const { s } = await computer.load(mod)
    expect(s).eq(longString)
  })
})

The last example shows how a string of arbitrary length can be stored using the module system. The idea is to partition a long string into constant size chunks, deploy each chunk in a separate module, and then deploy one additional module that recombined the modules for the chunks and exports their concatenation. In the example the "recombining" module is as follows:

import { c0 } from 'f86ec90ce2ab7367e197df1e63b45114a381d5636dc85e35dc28d721fbf0c228:0' // stores '000'
import { c1 } from 'ae90c7aa091045239d61011c770754b8cd8409541e56177d2a15e591e337bd67:0' // stores '000'
import { c2 } from '5530cfcc89fde62c2cfab4eea56e3aa2d41071480b7b094d7a01316776712701:0' // stores '000'
import { c3 } from 'dc63fbf200595012b239d69936ac63e4155040042ef7d2e6dff4ca49dec3f51e:0' // stores '0'
export const s = c0 + c1 + c2 + c3

Source