Offchain messages

Build, compile, sign, and verify messages offchain

Introduction

Any time you want one or more parties to approve of something that is not governed by an onchain program, you can prepare a message for them to sign offchain. Such messages can contain arbitrary contents, like the text of a plain-language contract, or some data encoded as text. Each offchain message contains a list of one or more signers that must provide a signature over that encoded message. Only when all of the required signers provide an authentic signature over the message are its contents considered to be ratified. You can use Kit to create, sign, verify, encode, and decode offchain messages.

Installation

Offchain message utilities are included within the @solana/kit library but you may also install them using their standalone packages.

To install the offchain message utilities:

npm install @solana/offchain-messages

What is an offchain message?

An offchain message consists of some UTF-8 message text, and a list of one or more required signers. The message is considered ratified when all of the signers provide a signature over the encoded message.

Here is an example of a contract being proposed by one person as an offchain message, and ratified by another.

import {
    ,
    ,
    ,
    ,
    ,
} from '@solana/kit';
 
// Ursula creates the contract as an offchain message.
const  = await ();
const :  = {
    :
        "Ursula grants Ariel three days as a human, in exchange for Ariel's voice, on the " +
        'condition that Ariel secures a kiss of true love from Prince Eric before the third ' +
        'sunset to remain human permanently. If Ariel fails, she reverts to a mermaid and ' +
        "becomes Ursula's property.",
    : [
        ,
        { : 'ARiEL3q7uXvN9yZK8s2a5GfpHmQdR7cBv' as  },
    ],
    : 1,
};
 
// Ursula partially signs the message, producing an offchain message envelope.
const  =
    await ();
 
// Ursula encodes the offchain message envelope to share with Ariel.
const  =
    ().();

Building offchain messages

Use the OffchainMessage type to help you create an offchain message.

Specifying the version

Specify the version property to select the schema and capabilities of the message. The latest version is version 1 which corresponds to the specification found in sRFC 3.

import { ,  } from '@solana/kit';
 
const :  = {
    : 1,
    /* ... */
};

The format and construction of v0 messages is beyond the scope of this guide. You can read the v0 specification here.

Required signatories

Each message must specify a list of addresses belonging to accounts that must sign the message in order for it to be considered valid.

Using a signer

You can declare one or more required signatories using a MessageSigner.

import { ,  } from '@solana/kit';
 
const  = await ();
const :  = {
    : 1,
    : [],
    /* ... */
};

When you do so, the message will have the capability to self-sign using the signOffchainMessageWithSigners method. Follow the instructions for signing offchain message envelopes with CryptoKeyPairs to sign it.

Using an address

You can declare one or more required signatories for whom you don't control the private key using the addresses of their accounts.

import { ,  } from '@solana/kit';
 
const :  = {
    : 1,
    : [{ : ('EkMpZ4tPqt7LgNCWjbNQ4WhuzFuitdwp8JPxSbCWXy9x') }],
    /* ... */
};

Defining the message content

Each message must contain some non-empty UTF-8 text the signatories must agree upon.

Using text

The content can be a string of UTF-8 text.

import { , ,  } from '@solana/kit';
 
const  = await ();
const :  = {
    : 1,
    : [
        ,
        { : ('r5FsobNdd53imrHH4rrdAt1MNkkJUjNPBTNqvKe9igR') },
    ],
    : '🥓 Crispy bacon is better than floppy bacon.',
};

Using data

You can also encode arbitrary data as text using an encoding such as base-64.

import { , , ,  } from '@solana/kit';
 
const  = await ();
const :  = {
    : 1,
    : [
        ,
        { : ('r5FsobNdd53imrHH4rrdAt1MNkkJUjNPBTNqvKe9igR') },
    ],
    : ().(),
};

Signing offchain messages

In order to be considered ratified an offchain message must be signed by all of the private keys belonging to accounts that are required signatories of the message.

Offchain messages whose signers are specified using MessageSigner objects have the ability to self-sign. This is because signers encapsulate both the address of the signing account as well as an implementation of the signing algorithm for the private key associated with that account.

The signOffchainMessageWithSigners method will return a new signed offchain message envelope of type FullySignedOffchainMessageEnvelope.

import {
    ,
    ,
    ,
} from '@solana/kit';
 
try {
    const  =
        await ();
} catch () {
    if ((, )) {
        .('Missing signers for the following addresses:', ..);
    } else {
        throw ;
    }
}

This function will throw if the offchain message does not carry a MessageSigner implementation for every required signer. To partially sign a message that you know to carry a strict subset of the required MessageSigners, use the partiallySignOffchainMessageWithSigners method.

Building offchain messages using MessageSigners is the recommended way to create self-signable offchain messages. To sign with a CryptoKey directly, you first have to compile the offchain message.

import {  } from '@solana/kit';
 
const  = ();

This produces an unsigned offchain message envelope. Follow the instructions for signing offchain message envelopes with CryptoKeyPairs to sign it.

If the version of the offchain message is known, use the compile function specific to that version, such as compileOffchainMessageV1Envelope. This will prevent you from bundling compilers you don't need, saving space in your JavaScript bundle.

Signing offchain message envelopes

Wherever you have a OffchainMessageEnvelope instead of an OffchainMessage you can add or replace a signature using the signOffchainMessageEnvelope method and one or more CryptoKeyPairs.

import {
    ,
    ,
    ,
} from '@solana/kit';
 
try {
    const  = await (
        [],
        ,
    );
} catch () {
    if ((, )) {
        .('Missing signers for the following addresses:', ..);
    } else {
        throw ;
    }
}

This function will throw if the resultant offchain message envelope is missing a signature for one of the offchain message's required signers. To partially sign an offchain message envelope, use the partiallySignOffchainMessageEnvelope method.

Verifying an offchain message

Given an offchain message envelope, you can verify that it has been signed by all of its required signatories using the verifyOffchainMessageEnvelope method.

import {
    ,
    ,
    ,
} from '@solana/kit';
 
try {
    await ();
} catch () {
    if ((, )) {
        if (...) {
            .(
                'The signatures for the following addresses are invalid',
                ..,
            );
        }
        if (...) {
            .(
                'The following required signatories have not signed this message',
                ..,
            );
        }
    } else {
        throw ;
    }
}

Verifying an offchain message will tell you if its content has been signed by all required signatories, but it will not ensure that the content of the message nor its list of required signatories is what you expect it to be. Take special care to inspect the content of the message before accepting it. If you have the original bytes of the message, you can compare them to the content of the envelope you are verifying. Otherwise, see deserializing offchain messages for instructions on how to decode the envelope's content for inspection.

Serializing offchain messages

If you would like to share an offchain message envelope with someone, you can serialize it to bytes using an encoder.

import {  } from '@solana/kit';
 
const  =
    ().();

Deserializing offchain messages

Decoding the bytes of an encoded offchain message envelope yields an OffchainMessageEnvelope object. This takes the form of a compiled offchain message encoded as OffchainMessageBytes, paired with a map between its required signatory addresses and their provided signatures, if any.

import {  } from '@solana/kit';
const  = ().(
    ,
);

Decoding the bytes of the offchain message envelope will yield an object containing an offchain message in its compiled form – a message in a form suitable for signing and transmitting over a network. Decompiling a compiled message will yield an OffchainMessage object. This is the most common form of offchain message that you will encounter when using Kit to build an application.

import {  } from '@solana/kit';
const  = ().(.);