Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ go test -race -coverprofile=coverage.out ./...
# Run a single test
go test -run TestNewTransferMessage ./...

# Build
# Build library
go build ./...

# Build CLI
go build ./cmd/tap/

# Vet
go vet ./...
```
Expand All @@ -30,7 +33,7 @@ This is a Go library that provides typed wrappers for all 20 TAP (Transaction Au

### Package structure

Single flat `tap` package — all types and helpers in the root. One file per message type.
Single flat `tap` package — all types and helpers in the root. One file per message type. CLI binary at `cmd/tap/`.

### Key types

Expand Down Expand Up @@ -94,6 +97,18 @@ Test vectors from `TAIPs/test-vectors/` are loaded in tests. The `TAIPs` directo

The `Agent.For` field uses `ForField` type, which handles JSON marshaling of both single DID strings and arrays of DIDs. Use `NewForField("did:eg:alice")` or `NewForField("did:eg:alice", "did:eg:bob")`.

## Pre-push checklist

**Always run formatting, linter, and tests locally before committing/pushing:**

```bash
go fmt ./...
golangci-lint run ./...
go test ./...
```

Fix any issues before committing. CI runs linter and tests and will block the PR if either fails.

## Development guidelines

- Every new message type needs: body struct, `TAPType()`, `New*Message()` constructor, a case in `ParseBody()`, and a matching `_test.go`
Expand All @@ -107,3 +122,37 @@ The `Agent.For` field uses `ForField` type, which handles JSON marshaling of bot
- **CHANGELOG.md** — Maintain a `CHANGELOG.md` in the project root using [Keep a Changelog](https://keepachangelog.com/) format. Update it with every user-facing change (new features, bug fixes, breaking changes, dependency updates). Group entries under `Added`, `Changed`, `Fixed`, `Removed` sections within version headings.
- **README.md** — Update `README.md` whenever changes affect public API, usage examples, installation instructions, or project capabilities.
- **CLAUDE.md** — Update this file whenever changes affect architecture, file layout, commands, dependencies, or development guidelines (e.g., new message types added to the file layout table, new commands, changed patterns).

## CLI (`cmd/tap/`)

The `tap` binary wraps all go-didcomm CLI commands and adds TAP-specific `message` and `receive` commands.

### CLI file layout

| File | Purpose |
|------|---------|
| `cmd/tap/main.go` | Entry point, command routing, usage text |
| `cmd/tap/message.go` | `message <type>` command — creates all 20 TAP message types |
| `cmd/tap/receive.go` | `receive` command — unpacks DIDComm envelope + parses TAP body |
| `cmd/tap/message_test.go` | Tests for message creation (all types, validation, file input) |
| `cmd/tap/receive_test.go` | Tests for receive (signed, authcrypt, pipe workflow) |

### CLI commands

```
tap did generate-key [--output-dir <dir>]
tap did generate-web --domain <d> [--path <p>] [--service-endpoint <url>] [--output-dir <dir>]
tap pack signed --key-file <f> [--send] [--did-doc <f>] [--message <m>]
tap pack anoncrypt [--send] [--did-doc <f>] [--message <m>]
tap pack authcrypt --key-file <f> [--send] [--did-doc <f>] [--message <m>]
tap unpack --key-file <f> [--did-doc <f>] [--message <m>]
tap send --to <url> [--message <m>]
tap message <type> --from <did> --to <did> [--thid <id>] [--body <json>]
tap receive --key-file <f> [--did-doc <f>] [--message <m>]
```

The `did`, `pack`, `unpack`, and `send` commands delegate to `go-didcomm/cli` package. The `message` and `receive` commands are TAP-specific.

### go-didcomm/cli package

Shared CLI utilities are exported from `go-didcomm/cli/`. Both the `didcomm` and `tap` binaries import this package. Key exports: `ReadMessageInput`, `BuildClient`, `BuildResolverWithOverrides`, `LoadKeyFile`, `MarshalDIDDoc`, `MarshalKeyPair`, `DetectContentType`, `ParseMessage`, `RunDID`, `RunPack`, `RunUnpack`, `RunSend`.
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@ Go library for the [Transaction Authorization Protocol (TAP)](https://tap.rsvp)

## Installation

### Library

```bash
go get github.com/TransactionAuthorizationProtocol/tap-go
```

### CLI

```bash
go install github.com/TransactionAuthorizationProtocol/tap-go/cmd/tap@latest
```

## How It Works

`tap-go` sits on top of [go-didcomm](https://github.com/Notabene-id/go-didcomm), which handles DIDComm v2 message packing (signing, encryption) and unpacking. This library adds TAP-specific typed bodies, validation, and parsing.
Expand Down Expand Up @@ -275,6 +283,67 @@ body, err := tap.ParseBody(msg) // (TAPBody, error)
body.TAPType() // e.g. "https://tap.rsvp/schema/1.0#Transfer"
```

## CLI

The `tap` CLI wraps all [go-didcomm](https://github.com/Notabene-id/go-didcomm) CLI commands and adds TAP-specific message creation and receiving.

### Commands

```
tap did generate-key [--output-dir <dir>]
tap did generate-web --domain <d> [--path <p>] [--service-endpoint <url>] [--output-dir <dir>]
tap pack signed --key-file <f> [--send] [--did-doc <f>] [--message <m>]
tap pack anoncrypt [--send] [--did-doc <f>] [--message <m>]
tap pack authcrypt --key-file <f> [--send] [--did-doc <f>] [--message <m>]
tap unpack --key-file <f> [--did-doc <f>] [--message <m>]
tap send --to <url> [--message <m>]
tap message <type> --from <did> --to <did> [--thid <id>] [--body <json>]
tap receive --key-file <f> [--did-doc <f>] [--message <m>]
```

### Create and pack a TAP message

```bash
# Generate identities
tap did generate-key --output-dir alice
tap did generate-key --output-dir bob

ALICE=$(jq -r .id alice/did-doc.json)
BOB=$(jq -r .id bob/did-doc.json)

# Create a TAP transfer message
tap message transfer --from $ALICE --to $BOB \
--body '{"asset":"eip155:1/slip44:60","amount":"1.5","agents":[{"@id":"'$ALICE'","role":"OriginatingVASP"}]}'

# Pipe: create → pack → send
tap message transfer --from $ALICE --to $BOB --body @body.json | \
tap pack authcrypt --key-file alice/keys.json
```

### Receive a TAP message

```bash
# Unpack a DIDComm envelope and parse the TAP body
echo '<packed-message>' | tap receive --key-file bob/keys.json
```

The `receive` command outputs JSON with the unpacked message, typed body, and envelope metadata (`encrypted`, `signed`, `anonymous`).

### TAP message types

**Initiating (no `--thid`):** `transfer`, `payment`, `exchange`, `escrow`, `connect`

**Reply (require `--thid`):** `authorize`, `authorization-required`, `settle`, `reject`, `cancel`, `revert`, `capture`, `quote`, `add-agents`, `remove-agent`, `replace-agent`, `update-agent`, `update-party`, `update-policies`, `confirm-relationship`

### Body input (`--body` flag)

- `'{"json"}'` — inline JSON string
- `@file.json` — read from file
- `-` — read from stdin
- _(omitted)_ — defaults to `{}`

The body JSON should contain only message-specific fields (e.g. `asset`, `amount`, `agents`). The CLI automatically sets `@context` and `@type`.

## Error Handling

```go
Expand Down
12 changes: 6 additions & 6 deletions authorize.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import (

// AuthorizeBody represents the body of a TAP Authorize message (TAIP-4).
type AuthorizeBody struct {
Context string `json:"@context"`
Type string `json:"@type"`
SettlementAddress string `json:"settlementAddress,omitempty"`
SettlementAsset string `json:"settlementAsset,omitempty"`
Amount string `json:"amount,omitempty"`
Expiry string `json:"expiry,omitempty"`
Context string `json:"@context"`
Type string `json:"@type"`
SettlementAddress string `json:"settlementAddress,omitempty"`
SettlementAsset string `json:"settlementAsset,omitempty"`
Amount string `json:"amount,omitempty"`
Expiry string `json:"expiry,omitempty"`
}

func (b *AuthorizeBody) TAPType() string { return TypeAuthorize }
Expand Down
101 changes: 101 additions & 0 deletions cmd/tap/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package main

import (
"fmt"
"os"

"github.com/Notabene-id/go-didcomm/cli"
)

const version = "0.1.0"

const usage = `tap - TAP (Transaction Authorization Protocol) CLI

Usage:
tap <command> [options]

Commands:
did generate-key Generate a did:key identity
did generate-web --domain <d> [--path <p>] Generate a did:web identity
pack signed --key-file <f> [--send] [--did-doc <f>] Sign a message (JWS)
pack anoncrypt [--send] [--did-doc <f>] [--message <m>] Anonymous encrypt (JWE)
pack authcrypt --key-file <f> [--send] [--did-doc <f>] Sign-then-encrypt
unpack --key-file <f> [--did-doc <f>] Unpack a message
send --to <url> [--message <m>] HTTP POST pre-packed message
message <type> --from <did> --to <did> [flags] Create a TAP message
receive --key-file <f> [--did-doc <f>] Unpack + parse TAP body
version Print version
help Print this help

TAP message types:
Initiating: transfer, payment, exchange, escrow, connect
Reply: authorize, authorization-required, settle, reject, cancel,
revert, capture, quote, add-agents, remove-agent,
replace-agent, update-agent, update-party, update-policies,
confirm-relationship

Message flags:
--from <did> Sender DID (required)
--to <did> Recipient DID(s), comma-separated (required)
--thid <id> Thread ID (required for reply messages)
--body <json> Body JSON: inline, @file.json, or - for stdin (default: {})

Message input (--message flag):
- Read from stdin (default)
@file.json Read from file
'{"json"}' Inline JSON string

Examples:
# Generate identities
tap did generate-key --output-dir alice
tap did generate-key --output-dir bob

# Create a TAP transfer message
ALICE=$(jq -r .id alice/did-doc.json)
BOB=$(jq -r .id bob/did-doc.json)
tap message transfer --from $ALICE --to $BOB \
--body '{"asset":"eip155:1/slip44:60","amount":"1.5","agents":[{"@id":"'$ALICE'","role":"OriginatingVASP"}]}'

# Pipe: create message → pack → send
tap message transfer --from $ALICE --to $BOB --body @body.json | \
tap pack authcrypt --key-file alice/keys.json

# Receive: unpack + parse TAP body
echo '<packed-message>' | tap receive --key-file bob/keys.json
`

func main() {
if len(os.Args) < 2 {
fmt.Fprint(os.Stderr, usage)
os.Exit(1)
}

var err error
switch os.Args[1] {
case "did":
err = cli.RunDID(os.Args[2:])
case "pack":
err = cli.RunPack(os.Args[2:])
case "unpack":
err = cli.RunUnpack(os.Args[2:])
case "send":
err = cli.RunSend(os.Args[2:])
case "message":
err = runMessage(os.Args[2:])
case "receive":
err = runReceive(os.Args[2:])
case "version":
fmt.Println("tap " + version)
case "help", "--help", "-h":
fmt.Print(usage)
default:
fmt.Fprintln(os.Stderr, "unknown command: "+os.Args[1]+"\n") //nolint:gosec // CLI stderr output
fmt.Fprint(os.Stderr, usage)
os.Exit(1)
}

if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
Loading