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.
- Creator calls
create_agreementwith parties, content hash, and metadata. Avault_funder(creator or third-party sponsor) pays account rent. - Each party calls
sign_agreement. Partial signatures are recorded on-chain. - When the last party signs (providing a
metadata_uri), the program mints an MPL-Core NFT viaCreateV2CPI in the same transaction. - To revoke, all parties call
vote_revoke. On the final unanimous vote, the NFT is burned viaBurnV1and the agreement is markedRevoked— theAgreementStateaccount 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
| Network | Program ID |
|---|---|
| Devnet | DF1cHTN9EE8Qonda1esTeYvFjmbYcoc52vDTjTMKvS1P |
| 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 |
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
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.
| 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 |
On-chain (trustless):
- Signatures are unforgeable — only the wallet holder can submit a valid
sign_agreement AgreementStateis immutable afterCompletedorRevokedcontent_hashintegrity: any observer can verify the original document against the on-chain hash
Off-chain (trusted but replaceable):
content_hashcorrectness 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.
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_authorityPDA (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.
- Rust +
solana-cli(2.2.x) - Anchor CLI (0.32.x)
- Node.js 18+ + Yarn
anchor buildanchor testThe 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
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
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.
GPL-3.0