Skip to content

@futureverse/artm

Asset Register Transaction Message codec — encodes batched link operations as a single signed Ethereum message.

Futureverse winddown

Futureverse (the publisher of @futureverse/*) is in winddown. The hosted services this package talked to (auth.futureverse.app, pass-api.futureverse.app, signer.futureverse.app) have been re-hosted by gen3labs at futurepass.gen3labs.tech (issuer) and fpsigner.gen3labs.tech (signer). For new projects, switch to the matching @gen3labs/* package — drop-in surface, default URLs already point at the gen3labs infra. The Asset Register API has no community replacement yet; see the Migration playbook for the per-package map.

Version
2.3.2
Published
2025-05-26
License
n/a
Status
fv-winddown
npm
https://www.npmjs.com/package/@futureverse/artm
Types
./index.d.ts
Maintainers
admin-futureverse, garethdainesnpm, jcsanpedro
Depends on
viem · apg-js · @futureverse/signer
Recent versions
2.3.0-beta.0 · 2.4.0-beta.0 · 2.2.6 · 2.2.7 · 2.3.0 · 2.3.1 · 2.4.0-beta.1 · 2.3.2
## Technical notes

Why use it

You write to the Asset Register. ARTM is the canonical message format the AR API accepts.

When to skip it

Read-only AR usage.

Pairs with

Example

ts
import { ARTM } from '@futureverse/artm';

const message = new ARTM().link({ parent, child, slot: 'head' }).build();
const sig = await signer.signMessage(message);

Gotchas

  • ZK-rollup-style sequence: each operation is appended; order matters.

Upstream README

ARTM

Asset Register Transaction Message (ARTM) describes the standard approach for parsing updates to the off-chain Asset Register database. One ARTM can describe multiple sequential operations on the Asset Register. A ZK-rollup of these transactions will be pushed to The Root Network to validate the updates made to the Asset Register periodically.

Quick start

Create an ARTM with a single operation

ts
const artm = new ARTM({
  statement: 'An update is being made to your inventory',
  operations: [
    {
      type: 'asset-link',
      action: 'create',
      args: [
        'equipWith_asmBrain',
        'did:fv-asset:1:evm:0x6bca6de2dbdc4e0d41f7273011785ea16ba47182:1000',
        'did:fv-asset:1:evm:0x1ea66a857de297471bc12dd12d93853ff6617284:21',
      ],
    },
  ],
  address: '0x6bca6de2dbdc4e0d41f7273011785ea16ba47182',
  nonce: 0,
})

console.log(artm.statement) // An update is being made to your inventory
console.log(artm.address) // 0x6bca6de2dbdc4e0d41f7273011785ea16ba47182
console.log(artm.nonce) // 0
console.log(artm.operations) // [ { type: 'asset-link', action: 'create', args: [Array] } ]

Create an ARTM and add multiple operations

ts
const artm = new ARTM({
  address: '0x6bca6de2dbdc4e0d41f7273011785ea16ba47182',
  nonce: 0,
})
  .addOperation({
    type: 'asset-link',
    action: 'delete',
    args: [
      'equipWith_asmBrain',
      'did:fv-asset:1:evm:0x6bca6de2dbdc4e0d41f7273011785ea16ba47182:1000',
      'did:fv-asset:1:evm:0x1ea66a857de297471bc12dd12d93853ff6617284:20',
    ],
  })
  .addOperation({
    type: 'asset-link',
    action: 'create',
    args: [
      'equipWith_asmBrain',
      'did:fv-asset:1:evm:0x6bca6de2dbdc4e0d41f7273011785ea16ba47182:1000',
      'did:fv-asset:1:evm:0x1ea66a857de297471bc12dd12d93853ff6617284:21',
    ],
  })

console.log(artm.operations.length) // 2

Add a signature to an ARTM

ts
const artm = new ARTM({
  operations: [
    {
      type: 'asset-link',
      action: 'create',
      args: [
        'equipWith_asmBrain',
        'did:fv-asset:1:evm:0x6bca6de2dbdc4e0d41f7273011785ea16ba47182:1000',
        'did:fv-asset:1:evm:0x1ea66a857de297471bc12dd12d93853ff6617284:21',
      ],
    },
  ],
  address: '0xaebC048B4D219D6822C17F1fe06E36Eba67D4144',
  nonce: 0,
}).setSignature(
  '0xe3c7ca6fb3a93e0b043f5653d1b0e95cf9976b97d6efc25a4d1cbcba008d2e2724cdb2a0273518ab719705bbdeebc228eda25c4ad2b01b70addb29bd132235481c',
)

console.log(await artm.verify()) // true

Now all together

ts
const artm = new ARTM({
  address: '0xaebC048B4D219D6822C17F1fe06E36Eba67D4144',
  nonce: 0,
})
  .addOperation({
    type: 'asset-link',
    action: 'create',
    args: [
      'equipWith_asmBrain',
      'did:fv-asset:1:evm:0x6bca6de2dbdc4e0d41f7273011785ea16ba47182:1000',
      'did:fv-asset:1:evm:0x1ea66a857de297471bc12dd12d93853ff6617284:21',
    ],
  })
  .setSignature(
    '0xe3c7ca6fb3a93e0b043f5653d1b0e95cf9976b97d6efc25a4d1cbcba008d2e2724cdb2a0273518ab719705bbdeebc228eda25c4ad2b01b70addb29bd132235481c',
  )

console.log(await artm.verify()) // true

Motivation

When making updates to the Asset Register there needs to be a clearly defined transaction message standard that will be followed and can be used to reliably prove updates. This RFC takes inspiration from ERC-4361: Sign-In with Ethereum.

Specification

Asset Register Transaction Message generation works as follows:

  1. A list of asset register updates is first defined
  2. A message is created that starts with \x19Ethereum Signed Message:\n<length of message> as defined in ERC-191
  3. Each asset register update will then be defined sequentially in the order the updates should take place.
  4. The end of the message will have the address making the updates and the nonce.
  5. Sign message using the correct wallet
  6. Submit transaction to Asset Register

Transaction Hash

A transaction hash is generated based on the message + signature

ts
keccak256(message + signature)

If the signature is null then the transaction hash will also be null

Example Message

markdown
Asset Register transaction

An update is being made to your inventory

Operations:

asset-link delete

- equipWith_asmBrain
- did:fv-asset:1:evm:0x6bca6de2dbdc4e0d41f7273011785ea16ba47182:1000
- did:fv-asset:1:evm:0x1ea66a857de297471bc12dd12d93853ff6617284:20
  end

asset-link create

- equipWith_asmBrain
- did:fv-asset:1:evm:0x6bca6de2dbdc4e0d41f7273011785ea16ba47182:1000
- did:fv-asset:1:evm:0x1ea66a857de297471bc12dd12d93853ff6617284:21
  end

asset-link create

- equipWith_gloves
- did:fv-asset:1:evm:0x6bca6de2dbdc4e0d41f7273011785ea16ba47182:1000
- did:fv-asset:1:root:0x1ea66a857de297471bc12dd12d93853ff6617284:21
  end

ownership update

- did:fv-asset:1:evm:0x6bca6de2dbdc4e0d41f7273011785ea16ba47182:1000
  end

asset-link create

- equipwith_hairStyle
- did:fv-asset:1:evm:0x6bca6de2dbdc4e0d41f7273011785ea16ba47182:1000
- did:fv-asset:off-chain:0x6bca6de2dbdc4e0d41f7273011785ea16ba47182:1234
  end

Operations END

Address: 0x854A3E045Ac44a7f4A1726AdAC576029135DFdA7
Nonce:1

Informal Message Template

A Bash-like informal template of the full message is presented below for readability and ease of understanding. Field descriptions are provided in the following section.

bash
Asset Register transaction

${statement}

Operations:
${operations[0]["type"]} ${operations[0]["action"]}
- ${operations[0]["args"][0]} - ${operations[0]["args"][1]}
...
- ${operations[0]["args"][n]}

${operations[1]["type"]} ${operations[1]["action"]}
- ${operations[1]["args"][0]}
- ${operations[1]["args"][1]}
...
- ${operations[1]["args"][n]}
end

...

${operations[n]["type"]} ${operations[n]["action"]}
- ${operations[n]["args"][0]}
- ${operations[n]["args"][1]}
...
- ${operations[n]["args"][n]}
end

Operations END

Address: ${address}
Nonce: ${nonce}

Message Field Descriptions

FieldDescription
statement(optional) is a human-readable ASCII assertion that the user will sign, and it must not contain '\n' (the byte 0x0a).
noncean incremented number for each address that is kept track of by the Asset Register to mitigate replay attacks.
addressis the Ethereum address performing the signing conformant to capitalization encoded checksum specified in https://eips.ethereum.org/EIPS/eip-55 where applicable?
operationsan array of updated objects with type, action and args

ABNF

The message MUST conform with the following Augmented Backus–Naur Form (ABNF, RFC 5234) expression (note that %s denotes case sensitivity for a string term, as per RFC 7405).

abnf
artm =
  %s"Asset Register transaction" LF
  LF
  [ statement LF ]
  LF
  %s"Operations:" LF
  LF
  operations
  %s"Operations END" LF
  LF
  %s"Address: " address LF
  %s"Nonce: " nonce

statement = 1*( reserved / unreserved / " " ) ; The purpose is to exclude LF (line breaks).

operations = *operation

operation = operation-type SP operation-action LF 1*(operation-argument) %s"end" 2*2LF

operation-type = *(ALPHA / "-")

operation-action = *(ALPHA / "-")

operation-argument = "-" SP *VCHAR LF

address = "0x" 40HEXDIG

nonce = 1*DIGIT

; ------------------------------------------------------------------------------
; RFC 3986

unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
reserved      = gen-delims / sub-delims
gen-delims    = ":" / "/" / "?" / "#" / "[" / "]" / "@"
sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
              / "*" / "+" / "," / ";" / "="

; ------------------------------------------------------------------------------
; RFC 5234

ALPHA          =  %x41-5A / %x61-7A   ; A-Z / a-z
LF             =  %x0A ; linefeed
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
DIGIT = %x30-39 ; 0-9
SP = %x20 ; space
VCHAR =  %x21-7E ; visible (printing) characters

Typescript Interface

ts
interface ARTM {
  statement: string
  operations: {
    type: string
    action: string
    args: string[]
  }[]
  address: string
  nonce: number
  signature?: string
  transactionHash: string | null
  addSignature: () => void
  verify: () => boolean
}

Curated independently by Codeology. Source-attributed reference for The Root Network. Not affiliated with Futureverse / TRN Labs.