Skip to content

PinkPlants/pactum

Repository files navigation

Pactum Protocol

Trustless on-chain agreement credentials on Solana.

Pactum is a Solana program (Anchor 0.32 / MPL-Core) that creates, co-signs, and permanently records legally-meaningful agreements as NFT credentials. Once all named parties sign, a Metaplex Core NFT is minted atomically in the same transaction. Credential validity is provable from chain state alone — no off-chain service required.


How It Works

  1. Creator calls create_agreement with parties, content hash, and metadata. A vault_funder (creator or third-party sponsor) pays account rent.
  2. Each party calls sign_agreement. Partial signatures are recorded on-chain.
  3. When the last party signs (providing a metadata_uri), the program mints an MPL-Core NFT via CreateV2 CPI in the same transaction.
  4. To revoke, all parties call vote_revoke. On the final unanimous vote, the NFT is burned via BurnV1 and the agreement is marked Revoked — the AgreementState account is preserved as a permanent audit trail.
PendingSignatures  ──sign_agreement──▶  Completed  ──vote_revoke (unanimous)──▶  Revoked
       │                                                                           (NFT burned;
       │                                                                            AgreementState kept)
       ├── cancel_agreement ──▶  Cancelled
       └── (deadline passes) ──▶  Expired

Program ID

Network Program ID
Devnet DF1cHTN9EE8Qonda1esTeYvFjmbYcoc52vDTjTMKvS1P

Instructions

Instruction Who can call Effect
initialize_collection Creator (once per wallet) Creates MPL-Core collection via CreateCollectionV1 CPI; stores CollectionState PDA; rent paid by vault_funder
create_agreement Creator Initializes AgreementState PDA; status → PendingSignatures; rent paid by vault_funder
sign_agreement Any party Records signature; final signer triggers MPL-Core CreateV2 mint
cancel_agreement Creator only Status → Cancelled (PendingSignatures only)
expire_agreement Anyone After deadline; status → Expired
vote_revoke Any party (Completed) Records revoke vote; unanimous → BurnV1 + status → Revoked
retract_revoke_vote Any party who voted Removes own revoke vote; max 2 retracts per party

Key Accounts

AgreementState (PDA: [b"agreement", creator, agreement_id])

creator          Pubkey
agreement_id     [u8; 16]    UUID v4 (client-generated)
content_hash     [u8; 32]    SHA-256 of agreement document
title            String      ≤ 64 bytes; used in NFT name
storage_uri      String      ≤ 128 bytes; Arweave TX ID or IPFS CID
storage_backend  enum        Ipfs | Arweave
parties          Vec<Pubkey> ≤ 10 parties
signed_by        Vec<Pubkey> grows with each sign_agreement
signed_at        Vec<i64>    parallel timestamp vec
status           enum        Draft | PendingSignatures | Completed | Cancelled | Expired | Revoked
created_at       i64         unix timestamp at creation
expires_at       i64         signing deadline (created_at + expires_in_secs)
completed_at     Option<i64> set when last party signs
revoked_at       Option<i64> set on unanimous revoke vote
nft_asset        Option<Pubkey>  set at mint; retained after burn (audit trail)
collection       Pubkey      MPL-Core collection from CollectionState
vault_funder     Pubkey      account that paid rent; validated in all subsequent instructions
revoke_votes     Vec<Pubkey>
revoke_retract_counts  Vec<RevokeRetractEntry>  struct { pubkey: Pubkey, count: u8 }
bump             u8          PDA bump seed

CollectionState (PDA: [b"collection", creator])

creator          Pubkey
collection_mint  Pubkey   MPL-Core collection asset address
bump             u8       PDA bump seed

One per creator. The program PDA ([b"mint_authority", b"v1", vault_funder]) holds MPL-Core update authority and signs all mint/burn CPIs via invoke_signed.


Constants

Constant Value Purpose
MAX_PARTIES 10 Max signers per agreement
MAX_EXPIRY_SECONDS 7,776,000 (90 days) Max signing deadline
DEFAULT_EXPIRY_SECONDS 2,592,000 (30 days) Default deadline
MAX_URI_LEN 128 Max storage URI / metadata URI length
MAX_TITLE_LEN 64 Max agreement title length
MAX_COLLECTION_NAME_LEN 32 Max collection name length
MAX_REVOKE_RETRACTS 2 Max times a party can retract their revoke vote

Trust Model

On-chain (trustless):

  • Signatures are unforgeable — only the wallet holder can submit a valid sign_agreement
  • AgreementState is immutable after Completed or Revoked
  • content_hash integrity: any observer can verify the original document against the on-chain hash

Off-chain (trusted but replaceable):

  • content_hash correctness relies on honest client software computing the SHA-256
  • Notification delivery (email, push) requires a backend
  • Transaction construction — mitigated by open-source client and dual-layer hash verification

If the Pactum backend goes offline, all credentials remain valid and independently verifiable from chain state in perpetuity.


NFT Format

Assets are minted via MPL-Core CreateV2 with:

  • Plugins: Attributes (agreement metadata) + PermanentTransferDelegate (non-transferable)
  • Name: "Pactum #{first_4_bytes_hex} — {title}"
  • Owner / Update Authority: pda_authority PDA (program-controlled)
  • URI: Arweave/IPFS URL to metadata.json (uploaded by backend before final sign)

Collection assets are supported — creators can group all their credentials under a named MPL-Core collection via initialize_collection.


Development

Prerequisites

Build

anchor build

Test

anchor test

The test suite loads the MPL-Core program fixture from tests/fixtures/mpl_core.so into the local validator, then runs 54 TypeScript tests covering all instructions and edge cases:

wave 1 scaffold setup         (1 test)
initialize_collection         (5 tests)
create_agreement              (13 tests)
sign_agreement                (11 tests)
cancel_agreement              (5 tests)
expire_agreement              (5 tests)
close_agreement               (3 tests)
vote_revoke                   (7 tests)
retract_revoke_vote           (3 tests)
collection path E2E           (1 test)
────────────────────
54 total

Project Structure

programs/pactum/src/
├── lib.rs                          Program entry points (7 instructions)
├── constants.rs                    Protocol constants
├── errors.rs                       Error codes
├── state/mod.rs                    AgreementState, CollectionState, enums
├── helpers.rs                      build_attributes(), hex_encode()
└── instructions/
    ├── initialize_collection.rs
    ├── create_agreement.rs
    ├── sign_agreement.rs           Partial sign + final MPL-Core CreateV2 CPI
    ├── cancel_agreement.rs
    ├── expire_agreement.rs
    ├── vote_revoke.rs              Partial vote + unanimous BurnV1 CPI
    └── retract_revoke_vote.rs
tests/
├── helpers.ts                      createAgreement(), PDA derivation helpers
├── pactum.ts                       initialize_collection + create_agreement tests
├── sign.ts                         sign_agreement tests
├── cancel_expire_close.ts          cancel / expire / close tests
└── revoke.ts                       vote_revoke / retract + E2E lifecycle test

Specification

Full protocol specification: pactum_spec.md

Topics covered: account structures, instruction logic flows, PDA seeds, CPI patterns, NFT metadata schema, security considerations, backend integration points, compute budget estimates.


License

GPL-3.0

About

Trustless on-chain protocol for creating, co-signing, and permanently recording legally-meaningful agreements as NFT credentials on Solana.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors