@proveanything/smartlinks 1.16.2 → 1.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/proof.d.ts +13 -3
- package/dist/api/proof.js +14 -4
- package/dist/docs/API_SUMMARY.md +50 -6
- package/dist/docs/proof-claiming-methods.md +16 -0
- package/dist/docs/proof-comms-triggers.md +137 -0
- package/dist/docs/proof-ownership-transfer.md +39 -9
- package/dist/openapi.yaml +47 -0
- package/dist/types/proof.d.ts +70 -3
- package/docs/API_SUMMARY.md +50 -6
- package/docs/proof-claiming-methods.md +16 -0
- package/docs/proof-comms-triggers.md +137 -0
- package/docs/proof-ownership-transfer.md +39 -9
- package/openapi.yaml +47 -0
- package/package.json +1 -1
package/dist/api/proof.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ProofResponse, ProofCreateRequest, ProofUpdateRequest, ProofValuesUpdateRequest, ProofClaimRequest, ProofGrant, CreateGrantOptions, RedeemGrantOptions, RedeemGrantResult, ProofTransfer, TransferProofOptions, TransferProofResult } from "../types/proof";
|
|
1
|
+
import { ProofResponse, ProofCreateRequest, ProofUpdateRequest, ProofValuesUpdateRequest, ProofClaimRequest, ProofGrant, CreateGrantOptions, RedeemGrantOptions, RedeemGrantResult, ProofTransfer, TransferProofOptions, TransferProofResult, AcceptTransferOptions, CancelTransferOptions } from "../types/proof";
|
|
2
2
|
export declare namespace proof {
|
|
3
3
|
/**
|
|
4
4
|
* Retrieves a single Proof by Collection ID, Product ID, and Proof ID.
|
|
@@ -177,6 +177,16 @@ export declare namespace proof {
|
|
|
177
177
|
* ```ts
|
|
178
178
|
* await proof.transfer(collectionId, productId, proofId, { release: true })
|
|
179
179
|
* ```
|
|
180
|
+
* Send comms by naming templates per role (server decides who receives each):
|
|
181
|
+
* ```ts
|
|
182
|
+
* await proof.transfer(collectionId, productId, proofId, {
|
|
183
|
+
* toEmail: 'buyer@example.com',
|
|
184
|
+
* comms: {
|
|
185
|
+
* recipient: { templateId: 'transfer-incoming', props: { note: 'Enjoy!' } },
|
|
186
|
+
* sender: { templateId: 'transfer-sent' },
|
|
187
|
+
* },
|
|
188
|
+
* })
|
|
189
|
+
* ```
|
|
180
190
|
*/
|
|
181
191
|
function transfer(collectionId: string, productId: string, proofId: string, options: TransferProofOptions): Promise<TransferProofResult>;
|
|
182
192
|
/**
|
|
@@ -184,12 +194,12 @@ export declare namespace proof {
|
|
|
184
194
|
* move — the proof's `userId` becomes the caller and the previous owner's private
|
|
185
195
|
* data and share grants are cleared/voided.
|
|
186
196
|
*/
|
|
187
|
-
function acceptTransfer(collectionId: string, productId: string, proofId: string): Promise<{
|
|
197
|
+
function acceptTransfer(collectionId: string, productId: string, proofId: string, options?: AcceptTransferOptions): Promise<{
|
|
188
198
|
ok: boolean;
|
|
189
199
|
proof: ProofResponse;
|
|
190
200
|
}>;
|
|
191
201
|
/** Cancel a pending push transfer (current owner / collection admin only). */
|
|
192
|
-
function cancelTransfer(collectionId: string, productId: string, proofId: string): Promise<{
|
|
202
|
+
function cancelTransfer(collectionId: string, productId: string, proofId: string, options?: CancelTransferOptions): Promise<{
|
|
193
203
|
ok: boolean;
|
|
194
204
|
}>;
|
|
195
205
|
/**
|
package/dist/api/proof.js
CHANGED
|
@@ -270,6 +270,16 @@ export var proof;
|
|
|
270
270
|
* ```ts
|
|
271
271
|
* await proof.transfer(collectionId, productId, proofId, { release: true })
|
|
272
272
|
* ```
|
|
273
|
+
* Send comms by naming templates per role (server decides who receives each):
|
|
274
|
+
* ```ts
|
|
275
|
+
* await proof.transfer(collectionId, productId, proofId, {
|
|
276
|
+
* toEmail: 'buyer@example.com',
|
|
277
|
+
* comms: {
|
|
278
|
+
* recipient: { templateId: 'transfer-incoming', props: { note: 'Enjoy!' } },
|
|
279
|
+
* sender: { templateId: 'transfer-sent' },
|
|
280
|
+
* },
|
|
281
|
+
* })
|
|
282
|
+
* ```
|
|
273
283
|
*/
|
|
274
284
|
async function transfer(collectionId, productId, proofId, options) {
|
|
275
285
|
return post(transferBase(collectionId, productId, proofId), Object.assign({}, options));
|
|
@@ -280,13 +290,13 @@ export var proof;
|
|
|
280
290
|
* move — the proof's `userId` becomes the caller and the previous owner's private
|
|
281
291
|
* data and share grants are cleared/voided.
|
|
282
292
|
*/
|
|
283
|
-
async function acceptTransfer(collectionId, productId, proofId) {
|
|
284
|
-
return post(`${transferBase(collectionId, productId, proofId)}/accept`, {});
|
|
293
|
+
async function acceptTransfer(collectionId, productId, proofId, options = {}) {
|
|
294
|
+
return post(`${transferBase(collectionId, productId, proofId)}/accept`, Object.assign({}, options));
|
|
285
295
|
}
|
|
286
296
|
proof.acceptTransfer = acceptTransfer;
|
|
287
297
|
/** Cancel a pending push transfer (current owner / collection admin only). */
|
|
288
|
-
async function cancelTransfer(collectionId, productId, proofId) {
|
|
289
|
-
return post(`${transferBase(collectionId, productId, proofId)}/cancel`, {});
|
|
298
|
+
async function cancelTransfer(collectionId, productId, proofId, options = {}) {
|
|
299
|
+
return post(`${transferBase(collectionId, productId, proofId)}/cancel`, Object.assign({}, options));
|
|
290
300
|
}
|
|
291
301
|
proof.cancelTransfer = cancelTransfer;
|
|
292
302
|
/**
|
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.16.
|
|
3
|
+
Version: 1.16.4 | Generated: 2026-09-05T14:13:52.478Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -35,6 +35,7 @@ For detailed guides on specific features:
|
|
|
35
35
|
- **[Proof Claiming Methods](proof-claiming-methods.md)** - All methods for claiming/registering product ownership (NFC tags, serial numbers, auto-generated claims)
|
|
36
36
|
- **[Proof Share Grants](proof-share-grants.md)** - Delegated, scoped, revocable bearer access to a single proof (read/comment/verify-owner links)
|
|
37
37
|
- **[Proof Ownership Transfer](proof-ownership-transfer.md)** - Moving a proof to a new owner: directed transfer, open release, accept/cancel, and the state machine
|
|
38
|
+
- **[Comms Triggers](proof-comms-triggers.md)** - Sending transactional comms from proof actions (claim/transfer/accept/cancel): the role→template map, per-action roles, and the trust boundary
|
|
38
39
|
- **[Lots](lots.md)** - Collection-scoped production groupings spanning many SKUs; facet/product selectors, member resolution, and GS1 AI(10) batch-then-lot resolution
|
|
39
40
|
- **[Item Context](item-context.md)** - The `itemContext` container prop derived from a serial-proof URL or NFC tap (what item the URL points at)
|
|
40
41
|
- **[Product Facets SDK](PRODUCT_FACETS_SDK.md)** - Admin and public product facet endpoints and TypeScript interfaces
|
|
@@ -7508,6 +7509,15 @@ interface ProofValuesUpdateRequest {
|
|
|
7508
7509
|
}
|
|
7509
7510
|
```
|
|
7510
7511
|
|
|
7512
|
+
**ProofClaimRequest** (interface)
|
|
7513
|
+
```typescript
|
|
7514
|
+
interface ProofClaimRequest {
|
|
7515
|
+
data?: Record<string, any>
|
|
7516
|
+
comms?: CommsTriggerMap
|
|
7517
|
+
[key: string]: any
|
|
7518
|
+
}
|
|
7519
|
+
```
|
|
7520
|
+
|
|
7511
7521
|
**ProofFieldsConfig** (interface)
|
|
7512
7522
|
```typescript
|
|
7513
7523
|
interface ProofFieldsConfig {
|
|
@@ -7580,13 +7590,29 @@ interface ProofTransfer {
|
|
|
7580
7590
|
}
|
|
7581
7591
|
```
|
|
7582
7592
|
|
|
7593
|
+
**CommsTrigger** (interface)
|
|
7594
|
+
```typescript
|
|
7595
|
+
interface CommsTrigger {
|
|
7596
|
+
templateId: string
|
|
7597
|
+
channel?: 'preferred' | 'email' | 'sms' | 'push' | 'wallet' | 'whatsapp'
|
|
7598
|
+
props?: Record<string, any>
|
|
7599
|
+
notify?: boolean
|
|
7600
|
+
appId?: string
|
|
7601
|
+
}
|
|
7602
|
+
```
|
|
7603
|
+
|
|
7583
7604
|
**TransferProofOptions** (interface)
|
|
7584
7605
|
```typescript
|
|
7585
7606
|
interface TransferProofOptions {
|
|
7607
|
+
* Comms to send. Roles: `recipient` (the named new owner) and `sender` (the
|
|
7608
|
+
* initiator) for a directed transfer; `owner` for an open release.
|
|
7609
|
+
comms?: CommsTriggerMap
|
|
7586
7610
|
toEmail?: string
|
|
7587
7611
|
toUserId?: string
|
|
7588
7612
|
toName?: string
|
|
7589
7613
|
release?: boolean
|
|
7614
|
+
* @deprecated The legacy fixed transfer email is gone. Put a note in a comms
|
|
7615
|
+
* trigger's props instead, e.g. `comms.recipient.props.note`.
|
|
7590
7616
|
message?: string
|
|
7591
7617
|
notify?: boolean
|
|
7592
7618
|
}
|
|
@@ -7601,12 +7627,26 @@ interface TransferProofResult {
|
|
|
7601
7627
|
}
|
|
7602
7628
|
```
|
|
7603
7629
|
|
|
7630
|
+
**AcceptTransferOptions** (interface)
|
|
7631
|
+
```typescript
|
|
7632
|
+
interface AcceptTransferOptions {
|
|
7633
|
+
comms?: CommsTriggerMap
|
|
7634
|
+
notify?: boolean
|
|
7635
|
+
}
|
|
7636
|
+
```
|
|
7637
|
+
|
|
7638
|
+
**CancelTransferOptions** (interface)
|
|
7639
|
+
```typescript
|
|
7640
|
+
interface CancelTransferOptions {
|
|
7641
|
+
comms?: CommsTriggerMap
|
|
7642
|
+
notify?: boolean
|
|
7643
|
+
}
|
|
7644
|
+
```
|
|
7645
|
+
|
|
7604
7646
|
**ProofResponse** = `Proof`
|
|
7605
7647
|
|
|
7606
7648
|
**ProofUpdateRequest** = `Partial<ProofWrite> & { proof?: ProofWrite }`
|
|
7607
7649
|
|
|
7608
|
-
**ProofClaimRequest** = `Record<string, any>`
|
|
7609
|
-
|
|
7610
7650
|
**ProofFieldScope** = `'public' | 'owner' | 'personal' | 'admin'`
|
|
7611
7651
|
|
|
7612
7652
|
**ProofFieldDef** = `ScopedFieldDef & { scope?: ProofFieldScope }`
|
|
@@ -7619,6 +7659,8 @@ interface TransferProofResult {
|
|
|
7619
7659
|
|
|
7620
7660
|
**ProofTransferState** = ``
|
|
7621
7661
|
|
|
7662
|
+
**CommsTriggerMap** = `Record<string, CommsTrigger>`
|
|
7663
|
+
|
|
7622
7664
|
### qr
|
|
7623
7665
|
|
|
7624
7666
|
**QrShortCodeLookupResponse** (interface)
|
|
@@ -10428,16 +10470,18 @@ Redeem a grant token (anonymous or signed-in). Records the redemption and return
|
|
|
10428
10470
|
productId: string,
|
|
10429
10471
|
proofId: string,
|
|
10430
10472
|
options: TransferProofOptions) → `Promise<TransferProofResult>`
|
|
10431
|
-
Start a push transfer of a proof (current owner / collection admin only). Directed — hand it to a named recipient who then calls {@link acceptTransfer}: ```ts await proof.transfer(collectionId, productId, proofId, { toEmail: 'buyer@example.com' }) ``` Open release — make the proof claimable by anyone: ```ts await proof.transfer(collectionId, productId, proofId, { release: true }) ```
|
|
10473
|
+
Start a push transfer of a proof (current owner / collection admin only). Directed — hand it to a named recipient who then calls {@link acceptTransfer}: ```ts await proof.transfer(collectionId, productId, proofId, { toEmail: 'buyer@example.com' }) ``` Open release — make the proof claimable by anyone: ```ts await proof.transfer(collectionId, productId, proofId, { release: true }) ``` Send comms by naming templates per role (server decides who receives each): ```ts await proof.transfer(collectionId, productId, proofId, { toEmail: 'buyer@example.com', comms: { recipient: { templateId: 'transfer-incoming', props: { note: 'Enjoy!' } }, sender: { templateId: 'transfer-sent' }, }, }) ```
|
|
10432
10474
|
|
|
10433
10475
|
**acceptTransfer**(collectionId: string,
|
|
10434
10476
|
productId: string,
|
|
10435
|
-
proofId: string
|
|
10477
|
+
proofId: string,
|
|
10478
|
+
options: AcceptTransferOptions = {}) → `Promise<`
|
|
10436
10479
|
Accept a directed transfer (the named recipient only). Completes the ownership move — the proof's `userId` becomes the caller and the previous owner's private data and share grants are cleared/voided.
|
|
10437
10480
|
|
|
10438
10481
|
**cancelTransfer**(collectionId: string,
|
|
10439
10482
|
productId: string,
|
|
10440
|
-
proofId: string
|
|
10483
|
+
proofId: string,
|
|
10484
|
+
options: CancelTransferOptions = {}) → `Promise<`
|
|
10441
10485
|
Cancel a pending push transfer (current owner / collection admin only).
|
|
10442
10486
|
|
|
10443
10487
|
**getTransfer**(collectionId: string,
|
|
@@ -289,6 +289,22 @@ try {
|
|
|
289
289
|
|
|
290
290
|
---
|
|
291
291
|
|
|
292
|
+
## Sending a claim confirmation
|
|
293
|
+
|
|
294
|
+
Claims can send a transactional confirmation once the proof is committed to the
|
|
295
|
+
ledger. Pass a `comms` map (role `claimer`) alongside `data` — you author the
|
|
296
|
+
template, the server hydrates `{{ proof }}` / `{{ product }}` / `{{ contact }}`:
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
await proof.claim(collectionId, productId, proofId, {
|
|
300
|
+
data: { store: 'Regent St' },
|
|
301
|
+
comms: { claimer: { templateId: 'welcome-owner', channel: 'preferred' } },
|
|
302
|
+
})
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Omit `comms` and nothing is sent (opt-in). Full detail:
|
|
306
|
+
[Comms Triggers](./proof-comms-triggers.md).
|
|
307
|
+
|
|
292
308
|
## API Reference
|
|
293
309
|
|
|
294
310
|
### `proof.claim(collectionId, productId, proofId, data?)`
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Comms Triggers
|
|
2
|
+
|
|
3
|
+
The standard way to send a **transactional message** as a side-effect of a proof
|
|
4
|
+
action — a claim confirmation, a "you've received a transfer" email, a
|
|
5
|
+
cancellation notice. You pass a **comms trigger** into the action; the server
|
|
6
|
+
sends it once the action's write is durable.
|
|
7
|
+
|
|
8
|
+
This replaces the old fixed SendGrid proof emails (`createProof` / `transferProof`).
|
|
9
|
+
Instead of a hardcoded template baked into the backend, **you author the template**
|
|
10
|
+
(in the template editor) and **name it per action**. The copy, branding, channel,
|
|
11
|
+
and merge fields are yours.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## The shape
|
|
16
|
+
|
|
17
|
+
An action takes a `comms` object: a **role → `CommsTrigger`** map.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
interface CommsTrigger {
|
|
21
|
+
templateId: string // the template to render + send (you author it)
|
|
22
|
+
channel?: 'preferred' | 'email' | 'sms' | 'push' | 'wallet' | 'whatsapp'
|
|
23
|
+
props?: Record<string, any> // merge data: a note, a message, custom fields
|
|
24
|
+
notify?: boolean // false → do the action, send nothing for this role
|
|
25
|
+
appId?: string // your app id, recorded in comms history
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// role → trigger
|
|
29
|
+
type CommsTriggerMap = Record<string, CommsTrigger>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Each action documents the **roles** it exposes (who can be messaged). You supply a
|
|
33
|
+
trigger only for the roles you want to notify.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## What you control vs what the server owns
|
|
38
|
+
|
|
39
|
+
You name a template and supply soft data. The server owns everything that decides
|
|
40
|
+
**who** receives it and **what context** the template can see. A caller can never
|
|
41
|
+
redirect a message to a contact of its choosing.
|
|
42
|
+
|
|
43
|
+
| You supply (per role) | The server injects |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `templateId` | which contact the role resolves to (recipient / sender / claimer / owner) |
|
|
46
|
+
| `props` (note, message, custom) | `{{ proof }}`, `{{ product }}`, `{{ contact }}` hydration |
|
|
47
|
+
| `channel`, `notify`, `appId` | the proof/product ids, the portal URL (`{{ proofUrl }}`) |
|
|
48
|
+
|
|
49
|
+
Templates are rendered with **Liquid**, so a template body can read
|
|
50
|
+
`{{ proof.values.owner.name }}`, `{{ product.title }}`, `{{ contact.firstName }}`,
|
|
51
|
+
`{{ proofUrl }}`, and any `props` you passed (`{{ note }}`). See
|
|
52
|
+
[Liquid Templates](./liquid-templates.md) and [Comms](./comms.md) for the template
|
|
53
|
+
model and channel resolution.
|
|
54
|
+
|
|
55
|
+
> This is a **1:1 transactional** send. It goes straight through the transactional
|
|
56
|
+
> comms path — it does **not** touch the interactions → segments → broadcasts
|
|
57
|
+
> (marketing/audience) pipeline. Recording a CRM interaction or awarding loyalty is
|
|
58
|
+
> a separate, independent concern; don't route those through the comms trigger.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Roles by action
|
|
63
|
+
|
|
64
|
+
| Action | Roles | Fired when |
|
|
65
|
+
|--------|-------|-----------|
|
|
66
|
+
| `claim` / `claimProduct` | `claimer` | after the proof is committed to the ledger |
|
|
67
|
+
| `transfer` (directed) | `recipient`, `sender` | on initiation |
|
|
68
|
+
| `transfer` (`release: true`) | `owner` | on initiation |
|
|
69
|
+
| `acceptTransfer` | `recipient` (new owner), `sender` (previous owner) | after the ownership move completes |
|
|
70
|
+
| `cancelTransfer` | `owner` (canceller), `recipient` (earmarked, directed only) | after the transfer is cancelled |
|
|
71
|
+
|
|
72
|
+
Timing is deliberate: comms fire **only after the durable write** (the ledger
|
|
73
|
+
commit for a claim, the ownership move for an accept). A failed action sends
|
|
74
|
+
nothing; a comms failure never breaks the action (best-effort).
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Examples
|
|
79
|
+
|
|
80
|
+
Directed transfer — notify the buyer, confirm to the seller:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
await proof.transfer(collectionId, productId, proofId, {
|
|
84
|
+
toEmail: 'buyer@example.com',
|
|
85
|
+
comms: {
|
|
86
|
+
recipient: { templateId: 'transfer-incoming', props: { note: 'Enjoy the watch!' } },
|
|
87
|
+
sender: { templateId: 'transfer-sent' },
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Claim — confirm to the claimer:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
await proof.claim(collectionId, productId, proofId, {
|
|
96
|
+
data: { purchaseStore: 'Regent St' },
|
|
97
|
+
comms: {
|
|
98
|
+
claimer: { templateId: 'welcome-owner', channel: 'preferred' },
|
|
99
|
+
},
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Accept — completion confirmation to both parties:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
await proof.acceptTransfer(collectionId, productId, proofId, {
|
|
107
|
+
comms: {
|
|
108
|
+
recipient: { templateId: 'transfer-complete-owner' },
|
|
109
|
+
sender: { templateId: 'transfer-complete-previous' },
|
|
110
|
+
},
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Suppress comms for one role while still messaging another:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
comms: {
|
|
118
|
+
recipient: { templateId: 'transfer-incoming' },
|
|
119
|
+
sender: { notify: false }, // do the transfer, don't email the seller
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Omit `comms` entirely and nothing is sent — comms are **opt-in**.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Notes
|
|
128
|
+
|
|
129
|
+
- **Email-only recipients work.** A directed transfer to a `toEmail` that isn't a
|
|
130
|
+
contact yet resolves/creates a contact before sending.
|
|
131
|
+
- **Consent + suppression apply.** Transactional sends still respect the contact's
|
|
132
|
+
channel consent and suppression list; a template's `topic` governs this.
|
|
133
|
+
- **Delivery is logged** to comms history (with your `appId` and a `ref`), so sends
|
|
134
|
+
are auditable per contact.
|
|
135
|
+
|
|
136
|
+
See also [Proof Ownership Transfer](./proof-ownership-transfer.md),
|
|
137
|
+
[Proof Claiming Methods](./proof-claiming-methods.md), and [Comms](./comms.md).
|
|
@@ -15,7 +15,7 @@ reserves states for them.
|
|
|
15
15
|
|
|
16
16
|
| Mode | How | Who completes it |
|
|
17
17
|
|------|-----|------------------|
|
|
18
|
-
| **Directed** | Owner names a recipient (`toEmail` / `toUserId`). The proof is earmarked for them (`claimUserId`)
|
|
18
|
+
| **Directed** | Owner names a recipient (`toEmail` / `toUserId`). The proof is earmarked for them (`claimUserId`); you notify them with a comms trigger. | The named recipient calls `acceptTransfer`. |
|
|
19
19
|
| **Open release** | Owner marks the proof `claimable`. | Anyone claims it via the normal claim flow. |
|
|
20
20
|
|
|
21
21
|
Only the current owner (or a collection admin) can start a transfer. A proof can
|
|
@@ -41,10 +41,14 @@ authoritative and atomic:
|
|
|
41
41
|
```ts
|
|
42
42
|
import { proof } from '@proveanything/smartlinks'
|
|
43
43
|
|
|
44
|
-
// Directed — hand it to a named buyer (they must accept)
|
|
44
|
+
// Directed — hand it to a named buyer (they must accept). Notify each party with a
|
|
45
|
+
// comms trigger naming a template you authored (see Comms Triggers below).
|
|
45
46
|
const { transfer } = await proof.transfer(collectionId, productId, proofId, {
|
|
46
47
|
toEmail: 'buyer@example.com',
|
|
47
|
-
|
|
48
|
+
comms: {
|
|
49
|
+
recipient: { templateId: 'transfer-incoming', props: { note: 'Enjoy the watch!' } },
|
|
50
|
+
sender: { templateId: 'transfer-sent' },
|
|
51
|
+
},
|
|
48
52
|
})
|
|
49
53
|
|
|
50
54
|
// …or an open release — anyone can now claim it
|
|
@@ -60,12 +64,18 @@ await proof.cancelTransfer(collectionId, productId, proofId)
|
|
|
60
64
|
## Recipient flow (directed)
|
|
61
65
|
|
|
62
66
|
```ts
|
|
63
|
-
// Only the named recipient can accept — a third party is rejected.
|
|
64
|
-
|
|
67
|
+
// Only the named recipient can accept — a third party is rejected. Optionally
|
|
68
|
+
// send completion comms to both parties.
|
|
69
|
+
const { proof: mine } = await proof.acceptTransfer(collectionId, productId, proofId, {
|
|
70
|
+
comms: {
|
|
71
|
+
recipient: { templateId: 'transfer-complete-owner' }, // the new owner
|
|
72
|
+
sender: { templateId: 'transfer-complete-previous' }, // the previous owner
|
|
73
|
+
},
|
|
74
|
+
})
|
|
65
75
|
```
|
|
66
76
|
|
|
67
77
|
The recipient sees the pending transfer on their account (a pending claim) and via
|
|
68
|
-
the
|
|
78
|
+
the notification you sent. Until they accept, the proof still belongs to the seller.
|
|
69
79
|
|
|
70
80
|
---
|
|
71
81
|
|
|
@@ -101,14 +111,34 @@ the email link. Until they accept, the proof still belongs to the seller.
|
|
|
101
111
|
added, are dispute-protected and never auto-transfer by default).
|
|
102
112
|
- **Every state change is audited** as an append-only attestation on the proof.
|
|
103
113
|
|
|
114
|
+
## Comms
|
|
115
|
+
|
|
116
|
+
Every transfer action takes an optional `comms` map (role → comms trigger) that
|
|
117
|
+
sends a transactional message once the action's write is durable. Roles:
|
|
118
|
+
|
|
119
|
+
| Action | Roles |
|
|
120
|
+
|--------|-------|
|
|
121
|
+
| `transfer` (directed) | `recipient`, `sender` |
|
|
122
|
+
| `transfer` (`release: true`) | `owner` |
|
|
123
|
+
| `acceptTransfer` | `recipient` (new owner), `sender` (previous owner) |
|
|
124
|
+
| `cancelTransfer` | `owner`, `recipient` (directed only) |
|
|
125
|
+
|
|
126
|
+
You author each template; the server owns who each role resolves to and hydrates
|
|
127
|
+
`{{ proof }}` / `{{ product }}` / `{{ contact }}` / `{{ proofUrl }}`. Pass
|
|
128
|
+
`notify: false` on a role (or omit `comms`) to send nothing. Full detail:
|
|
129
|
+
[Comms Triggers](./proof-comms-triggers.md).
|
|
130
|
+
|
|
104
131
|
## API
|
|
105
132
|
|
|
106
133
|
| Method | Endpoint |
|
|
107
134
|
|--------|----------|
|
|
108
135
|
| `proof.transfer(c, p, id, opts)` | `POST …/proof/:id/transfer` |
|
|
109
|
-
| `proof.acceptTransfer(c, p, id)` | `POST …/proof/:id/transfer/accept` |
|
|
110
|
-
| `proof.cancelTransfer(c, p, id)` | `POST …/proof/:id/transfer/cancel` |
|
|
136
|
+
| `proof.acceptTransfer(c, p, id, opts?)` | `POST …/proof/:id/transfer/accept` |
|
|
137
|
+
| `proof.cancelTransfer(c, p, id, opts?)` | `POST …/proof/:id/transfer/cancel` |
|
|
111
138
|
| `proof.getTransfer(c, p, id)` | `GET …/proof/:id/transfer` |
|
|
112
139
|
|
|
113
|
-
|
|
140
|
+
`opts` on transfer/accept/cancel carries the `comms` map (and `notify`).
|
|
141
|
+
|
|
142
|
+
See also [Comms Triggers](./proof-comms-triggers.md),
|
|
143
|
+
[Proof Share Grants](./proof-share-grants.md) and
|
|
114
144
|
[Proof Claiming Methods](./proof-claiming-methods.md).
|
package/dist/openapi.yaml
CHANGED
|
@@ -25717,6 +25717,14 @@ components:
|
|
|
25717
25717
|
type: object
|
|
25718
25718
|
additionalProperties:
|
|
25719
25719
|
$ref: "#/components/schemas/JsonValue"
|
|
25720
|
+
ProofClaimRequest:
|
|
25721
|
+
type: object
|
|
25722
|
+
properties:
|
|
25723
|
+
data:
|
|
25724
|
+
type: object
|
|
25725
|
+
additionalProperties: true
|
|
25726
|
+
comms:
|
|
25727
|
+
$ref: "#/components/schemas/CommsTriggerMap"
|
|
25720
25728
|
ProofFieldsConfig:
|
|
25721
25729
|
type: object
|
|
25722
25730
|
properties:
|
|
@@ -25844,9 +25852,34 @@ components:
|
|
|
25844
25852
|
- state
|
|
25845
25853
|
- createdAt
|
|
25846
25854
|
- updatedAt
|
|
25855
|
+
CommsTrigger:
|
|
25856
|
+
type: object
|
|
25857
|
+
properties:
|
|
25858
|
+
templateId:
|
|
25859
|
+
type: string
|
|
25860
|
+
channel:
|
|
25861
|
+
type: string
|
|
25862
|
+
enum:
|
|
25863
|
+
- preferred
|
|
25864
|
+
- email
|
|
25865
|
+
- sms
|
|
25866
|
+
- push
|
|
25867
|
+
- wallet
|
|
25868
|
+
- whatsapp
|
|
25869
|
+
props:
|
|
25870
|
+
type: object
|
|
25871
|
+
additionalProperties: true
|
|
25872
|
+
notify:
|
|
25873
|
+
type: boolean
|
|
25874
|
+
appId:
|
|
25875
|
+
type: string
|
|
25876
|
+
required:
|
|
25877
|
+
- templateId
|
|
25847
25878
|
TransferProofOptions:
|
|
25848
25879
|
type: object
|
|
25849
25880
|
properties:
|
|
25881
|
+
comms:
|
|
25882
|
+
$ref: "#/components/schemas/CommsTriggerMap"
|
|
25850
25883
|
toEmail:
|
|
25851
25884
|
type: string
|
|
25852
25885
|
toUserId:
|
|
@@ -25875,6 +25908,20 @@ components:
|
|
|
25875
25908
|
- ok
|
|
25876
25909
|
- mode
|
|
25877
25910
|
- transfer
|
|
25911
|
+
AcceptTransferOptions:
|
|
25912
|
+
type: object
|
|
25913
|
+
properties:
|
|
25914
|
+
comms:
|
|
25915
|
+
$ref: "#/components/schemas/CommsTriggerMap"
|
|
25916
|
+
notify:
|
|
25917
|
+
type: boolean
|
|
25918
|
+
CancelTransferOptions:
|
|
25919
|
+
type: object
|
|
25920
|
+
properties:
|
|
25921
|
+
comms:
|
|
25922
|
+
$ref: "#/components/schemas/CommsTriggerMap"
|
|
25923
|
+
notify:
|
|
25924
|
+
type: boolean
|
|
25878
25925
|
ProofTransferType:
|
|
25879
25926
|
type: string
|
|
25880
25927
|
enum:
|
package/dist/types/proof.d.ts
CHANGED
|
@@ -136,7 +136,20 @@ export interface ProofValuesUpdateRequest {
|
|
|
136
136
|
/** The caller's own private slot, merged into `proof.values.personal[callerUid]`. */
|
|
137
137
|
personal?: Record<string, JsonValue>;
|
|
138
138
|
}
|
|
139
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Claim/mint payload. Remains open (server-side rules vary), but two keys are
|
|
141
|
+
* recognised across the claim endpoints:
|
|
142
|
+
* - `data` — claim values written to the proof
|
|
143
|
+
* - `comms` — comms to send once the proof is committed to the ledger. Role:
|
|
144
|
+
* `claimer` (the claiming user). See {@link CommsTrigger}.
|
|
145
|
+
*/
|
|
146
|
+
export interface ProofClaimRequest {
|
|
147
|
+
/** Claim values written to the proof. */
|
|
148
|
+
data?: Record<string, any>;
|
|
149
|
+
/** Comms to send on claim; role `claimer`. Fired only after the ledger write. */
|
|
150
|
+
comms?: CommsTriggerMap;
|
|
151
|
+
[key: string]: any;
|
|
152
|
+
}
|
|
140
153
|
/**
|
|
141
154
|
* `'public'` (default, omitted) reads/writes `proof.values[key]`.
|
|
142
155
|
* `'owner'` reads/writes `proof.values.owner[key]`.
|
|
@@ -238,12 +251,44 @@ export interface ProofTransfer {
|
|
|
238
251
|
createdAt: string;
|
|
239
252
|
updatedAt: string;
|
|
240
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* A comms trigger — the standard way to have an action send a transactional
|
|
256
|
+
* message as a side-effect of doing its work. You name a template (authored in
|
|
257
|
+
* the template editor) and optionally supply merge props; the server owns who
|
|
258
|
+
* receives it and injects the authoritative context ({{ proof }}, {{ product }},
|
|
259
|
+
* the target contact). This is a 1:1 transactional send — it does not touch the
|
|
260
|
+
* interactions → segments → broadcasts (marketing) pipeline.
|
|
261
|
+
*/
|
|
262
|
+
export interface CommsTrigger {
|
|
263
|
+
/** The comms template to render and send. */
|
|
264
|
+
templateId: string;
|
|
265
|
+
/** Delivery channel; defaults to the contact's preferred channel. */
|
|
266
|
+
channel?: 'preferred' | 'email' | 'sms' | 'push' | 'wallet' | 'whatsapp';
|
|
267
|
+
/** Freeform merge data for the template (note, message, custom fields). */
|
|
268
|
+
props?: Record<string, any>;
|
|
269
|
+
/** Set false to skip this message while still performing the action. */
|
|
270
|
+
notify?: boolean;
|
|
271
|
+
/** Owning app id, recorded in comms history. */
|
|
272
|
+
appId?: string;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* A role → {@link CommsTrigger} map passed into an action. Each action documents
|
|
276
|
+
* the roles it exposes (e.g. a transfer exposes `recipient` and `sender`); only
|
|
277
|
+
* roles the action recognises are sent. The server decides which contact each
|
|
278
|
+
* role resolves to — the caller only names templates, never recipients.
|
|
279
|
+
*/
|
|
280
|
+
export type CommsTriggerMap = Record<string, CommsTrigger>;
|
|
241
281
|
/**
|
|
242
282
|
* Start a push transfer. Provide **one** of:
|
|
243
283
|
* - `toEmail` / `toUserId` — a directed transfer to a named recipient (they accept).
|
|
244
284
|
* - `release: true` — an open release (the proof becomes claimable by anyone).
|
|
245
285
|
*/
|
|
246
286
|
export interface TransferProofOptions {
|
|
287
|
+
/**
|
|
288
|
+
* Comms to send. Roles: `recipient` (the named new owner) and `sender` (the
|
|
289
|
+
* initiator) for a directed transfer; `owner` for an open release.
|
|
290
|
+
*/
|
|
291
|
+
comms?: CommsTriggerMap;
|
|
247
292
|
/** Directed: recipient email (created/looked up if needed). */
|
|
248
293
|
toEmail?: string;
|
|
249
294
|
/** Directed: recipient user id, if already known. */
|
|
@@ -252,9 +297,12 @@ export interface TransferProofOptions {
|
|
|
252
297
|
toName?: string;
|
|
253
298
|
/** Open release: mark the proof claimable instead of directing it. */
|
|
254
299
|
release?: boolean;
|
|
255
|
-
/**
|
|
300
|
+
/**
|
|
301
|
+
* @deprecated The legacy fixed transfer email is gone. Put a note in a comms
|
|
302
|
+
* trigger's props instead, e.g. `comms.recipient.props.note`.
|
|
303
|
+
*/
|
|
256
304
|
message?: string;
|
|
257
|
-
/** Set `false` to skip
|
|
305
|
+
/** Set `false` to skip ALL comms for this action (per-role: `comms.<role>.notify`). */
|
|
258
306
|
notify?: boolean;
|
|
259
307
|
}
|
|
260
308
|
/** Result of initiating a push transfer. */
|
|
@@ -263,3 +311,22 @@ export interface TransferProofResult {
|
|
|
263
311
|
mode: 'directed' | 'open_release';
|
|
264
312
|
transfer: ProofTransfer;
|
|
265
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Options for accepting a directed transfer. Comms roles: `recipient` (the
|
|
316
|
+
* accepting new owner) and `sender` (the previous owner) — the completion
|
|
317
|
+
* confirmation for each party.
|
|
318
|
+
*/
|
|
319
|
+
export interface AcceptTransferOptions {
|
|
320
|
+
comms?: CommsTriggerMap;
|
|
321
|
+
/** Set false to skip all completion comms. */
|
|
322
|
+
notify?: boolean;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Options for cancelling a pending transfer. Comms roles: `owner` (the canceller)
|
|
326
|
+
* and, for a directed transfer, `recipient` (whose earmarked transfer is withdrawn).
|
|
327
|
+
*/
|
|
328
|
+
export interface CancelTransferOptions {
|
|
329
|
+
comms?: CommsTriggerMap;
|
|
330
|
+
/** Set false to skip all cancellation comms. */
|
|
331
|
+
notify?: boolean;
|
|
332
|
+
}
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.16.
|
|
3
|
+
Version: 1.16.4 | Generated: 2026-09-05T14:13:52.478Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -35,6 +35,7 @@ For detailed guides on specific features:
|
|
|
35
35
|
- **[Proof Claiming Methods](proof-claiming-methods.md)** - All methods for claiming/registering product ownership (NFC tags, serial numbers, auto-generated claims)
|
|
36
36
|
- **[Proof Share Grants](proof-share-grants.md)** - Delegated, scoped, revocable bearer access to a single proof (read/comment/verify-owner links)
|
|
37
37
|
- **[Proof Ownership Transfer](proof-ownership-transfer.md)** - Moving a proof to a new owner: directed transfer, open release, accept/cancel, and the state machine
|
|
38
|
+
- **[Comms Triggers](proof-comms-triggers.md)** - Sending transactional comms from proof actions (claim/transfer/accept/cancel): the role→template map, per-action roles, and the trust boundary
|
|
38
39
|
- **[Lots](lots.md)** - Collection-scoped production groupings spanning many SKUs; facet/product selectors, member resolution, and GS1 AI(10) batch-then-lot resolution
|
|
39
40
|
- **[Item Context](item-context.md)** - The `itemContext` container prop derived from a serial-proof URL or NFC tap (what item the URL points at)
|
|
40
41
|
- **[Product Facets SDK](PRODUCT_FACETS_SDK.md)** - Admin and public product facet endpoints and TypeScript interfaces
|
|
@@ -7508,6 +7509,15 @@ interface ProofValuesUpdateRequest {
|
|
|
7508
7509
|
}
|
|
7509
7510
|
```
|
|
7510
7511
|
|
|
7512
|
+
**ProofClaimRequest** (interface)
|
|
7513
|
+
```typescript
|
|
7514
|
+
interface ProofClaimRequest {
|
|
7515
|
+
data?: Record<string, any>
|
|
7516
|
+
comms?: CommsTriggerMap
|
|
7517
|
+
[key: string]: any
|
|
7518
|
+
}
|
|
7519
|
+
```
|
|
7520
|
+
|
|
7511
7521
|
**ProofFieldsConfig** (interface)
|
|
7512
7522
|
```typescript
|
|
7513
7523
|
interface ProofFieldsConfig {
|
|
@@ -7580,13 +7590,29 @@ interface ProofTransfer {
|
|
|
7580
7590
|
}
|
|
7581
7591
|
```
|
|
7582
7592
|
|
|
7593
|
+
**CommsTrigger** (interface)
|
|
7594
|
+
```typescript
|
|
7595
|
+
interface CommsTrigger {
|
|
7596
|
+
templateId: string
|
|
7597
|
+
channel?: 'preferred' | 'email' | 'sms' | 'push' | 'wallet' | 'whatsapp'
|
|
7598
|
+
props?: Record<string, any>
|
|
7599
|
+
notify?: boolean
|
|
7600
|
+
appId?: string
|
|
7601
|
+
}
|
|
7602
|
+
```
|
|
7603
|
+
|
|
7583
7604
|
**TransferProofOptions** (interface)
|
|
7584
7605
|
```typescript
|
|
7585
7606
|
interface TransferProofOptions {
|
|
7607
|
+
* Comms to send. Roles: `recipient` (the named new owner) and `sender` (the
|
|
7608
|
+
* initiator) for a directed transfer; `owner` for an open release.
|
|
7609
|
+
comms?: CommsTriggerMap
|
|
7586
7610
|
toEmail?: string
|
|
7587
7611
|
toUserId?: string
|
|
7588
7612
|
toName?: string
|
|
7589
7613
|
release?: boolean
|
|
7614
|
+
* @deprecated The legacy fixed transfer email is gone. Put a note in a comms
|
|
7615
|
+
* trigger's props instead, e.g. `comms.recipient.props.note`.
|
|
7590
7616
|
message?: string
|
|
7591
7617
|
notify?: boolean
|
|
7592
7618
|
}
|
|
@@ -7601,12 +7627,26 @@ interface TransferProofResult {
|
|
|
7601
7627
|
}
|
|
7602
7628
|
```
|
|
7603
7629
|
|
|
7630
|
+
**AcceptTransferOptions** (interface)
|
|
7631
|
+
```typescript
|
|
7632
|
+
interface AcceptTransferOptions {
|
|
7633
|
+
comms?: CommsTriggerMap
|
|
7634
|
+
notify?: boolean
|
|
7635
|
+
}
|
|
7636
|
+
```
|
|
7637
|
+
|
|
7638
|
+
**CancelTransferOptions** (interface)
|
|
7639
|
+
```typescript
|
|
7640
|
+
interface CancelTransferOptions {
|
|
7641
|
+
comms?: CommsTriggerMap
|
|
7642
|
+
notify?: boolean
|
|
7643
|
+
}
|
|
7644
|
+
```
|
|
7645
|
+
|
|
7604
7646
|
**ProofResponse** = `Proof`
|
|
7605
7647
|
|
|
7606
7648
|
**ProofUpdateRequest** = `Partial<ProofWrite> & { proof?: ProofWrite }`
|
|
7607
7649
|
|
|
7608
|
-
**ProofClaimRequest** = `Record<string, any>`
|
|
7609
|
-
|
|
7610
7650
|
**ProofFieldScope** = `'public' | 'owner' | 'personal' | 'admin'`
|
|
7611
7651
|
|
|
7612
7652
|
**ProofFieldDef** = `ScopedFieldDef & { scope?: ProofFieldScope }`
|
|
@@ -7619,6 +7659,8 @@ interface TransferProofResult {
|
|
|
7619
7659
|
|
|
7620
7660
|
**ProofTransferState** = ``
|
|
7621
7661
|
|
|
7662
|
+
**CommsTriggerMap** = `Record<string, CommsTrigger>`
|
|
7663
|
+
|
|
7622
7664
|
### qr
|
|
7623
7665
|
|
|
7624
7666
|
**QrShortCodeLookupResponse** (interface)
|
|
@@ -10428,16 +10470,18 @@ Redeem a grant token (anonymous or signed-in). Records the redemption and return
|
|
|
10428
10470
|
productId: string,
|
|
10429
10471
|
proofId: string,
|
|
10430
10472
|
options: TransferProofOptions) → `Promise<TransferProofResult>`
|
|
10431
|
-
Start a push transfer of a proof (current owner / collection admin only). Directed — hand it to a named recipient who then calls {@link acceptTransfer}: ```ts await proof.transfer(collectionId, productId, proofId, { toEmail: 'buyer@example.com' }) ``` Open release — make the proof claimable by anyone: ```ts await proof.transfer(collectionId, productId, proofId, { release: true }) ```
|
|
10473
|
+
Start a push transfer of a proof (current owner / collection admin only). Directed — hand it to a named recipient who then calls {@link acceptTransfer}: ```ts await proof.transfer(collectionId, productId, proofId, { toEmail: 'buyer@example.com' }) ``` Open release — make the proof claimable by anyone: ```ts await proof.transfer(collectionId, productId, proofId, { release: true }) ``` Send comms by naming templates per role (server decides who receives each): ```ts await proof.transfer(collectionId, productId, proofId, { toEmail: 'buyer@example.com', comms: { recipient: { templateId: 'transfer-incoming', props: { note: 'Enjoy!' } }, sender: { templateId: 'transfer-sent' }, }, }) ```
|
|
10432
10474
|
|
|
10433
10475
|
**acceptTransfer**(collectionId: string,
|
|
10434
10476
|
productId: string,
|
|
10435
|
-
proofId: string
|
|
10477
|
+
proofId: string,
|
|
10478
|
+
options: AcceptTransferOptions = {}) → `Promise<`
|
|
10436
10479
|
Accept a directed transfer (the named recipient only). Completes the ownership move — the proof's `userId` becomes the caller and the previous owner's private data and share grants are cleared/voided.
|
|
10437
10480
|
|
|
10438
10481
|
**cancelTransfer**(collectionId: string,
|
|
10439
10482
|
productId: string,
|
|
10440
|
-
proofId: string
|
|
10483
|
+
proofId: string,
|
|
10484
|
+
options: CancelTransferOptions = {}) → `Promise<`
|
|
10441
10485
|
Cancel a pending push transfer (current owner / collection admin only).
|
|
10442
10486
|
|
|
10443
10487
|
**getTransfer**(collectionId: string,
|
|
@@ -289,6 +289,22 @@ try {
|
|
|
289
289
|
|
|
290
290
|
---
|
|
291
291
|
|
|
292
|
+
## Sending a claim confirmation
|
|
293
|
+
|
|
294
|
+
Claims can send a transactional confirmation once the proof is committed to the
|
|
295
|
+
ledger. Pass a `comms` map (role `claimer`) alongside `data` — you author the
|
|
296
|
+
template, the server hydrates `{{ proof }}` / `{{ product }}` / `{{ contact }}`:
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
await proof.claim(collectionId, productId, proofId, {
|
|
300
|
+
data: { store: 'Regent St' },
|
|
301
|
+
comms: { claimer: { templateId: 'welcome-owner', channel: 'preferred' } },
|
|
302
|
+
})
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Omit `comms` and nothing is sent (opt-in). Full detail:
|
|
306
|
+
[Comms Triggers](./proof-comms-triggers.md).
|
|
307
|
+
|
|
292
308
|
## API Reference
|
|
293
309
|
|
|
294
310
|
### `proof.claim(collectionId, productId, proofId, data?)`
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Comms Triggers
|
|
2
|
+
|
|
3
|
+
The standard way to send a **transactional message** as a side-effect of a proof
|
|
4
|
+
action — a claim confirmation, a "you've received a transfer" email, a
|
|
5
|
+
cancellation notice. You pass a **comms trigger** into the action; the server
|
|
6
|
+
sends it once the action's write is durable.
|
|
7
|
+
|
|
8
|
+
This replaces the old fixed SendGrid proof emails (`createProof` / `transferProof`).
|
|
9
|
+
Instead of a hardcoded template baked into the backend, **you author the template**
|
|
10
|
+
(in the template editor) and **name it per action**. The copy, branding, channel,
|
|
11
|
+
and merge fields are yours.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## The shape
|
|
16
|
+
|
|
17
|
+
An action takes a `comms` object: a **role → `CommsTrigger`** map.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
interface CommsTrigger {
|
|
21
|
+
templateId: string // the template to render + send (you author it)
|
|
22
|
+
channel?: 'preferred' | 'email' | 'sms' | 'push' | 'wallet' | 'whatsapp'
|
|
23
|
+
props?: Record<string, any> // merge data: a note, a message, custom fields
|
|
24
|
+
notify?: boolean // false → do the action, send nothing for this role
|
|
25
|
+
appId?: string // your app id, recorded in comms history
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// role → trigger
|
|
29
|
+
type CommsTriggerMap = Record<string, CommsTrigger>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Each action documents the **roles** it exposes (who can be messaged). You supply a
|
|
33
|
+
trigger only for the roles you want to notify.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## What you control vs what the server owns
|
|
38
|
+
|
|
39
|
+
You name a template and supply soft data. The server owns everything that decides
|
|
40
|
+
**who** receives it and **what context** the template can see. A caller can never
|
|
41
|
+
redirect a message to a contact of its choosing.
|
|
42
|
+
|
|
43
|
+
| You supply (per role) | The server injects |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `templateId` | which contact the role resolves to (recipient / sender / claimer / owner) |
|
|
46
|
+
| `props` (note, message, custom) | `{{ proof }}`, `{{ product }}`, `{{ contact }}` hydration |
|
|
47
|
+
| `channel`, `notify`, `appId` | the proof/product ids, the portal URL (`{{ proofUrl }}`) |
|
|
48
|
+
|
|
49
|
+
Templates are rendered with **Liquid**, so a template body can read
|
|
50
|
+
`{{ proof.values.owner.name }}`, `{{ product.title }}`, `{{ contact.firstName }}`,
|
|
51
|
+
`{{ proofUrl }}`, and any `props` you passed (`{{ note }}`). See
|
|
52
|
+
[Liquid Templates](./liquid-templates.md) and [Comms](./comms.md) for the template
|
|
53
|
+
model and channel resolution.
|
|
54
|
+
|
|
55
|
+
> This is a **1:1 transactional** send. It goes straight through the transactional
|
|
56
|
+
> comms path — it does **not** touch the interactions → segments → broadcasts
|
|
57
|
+
> (marketing/audience) pipeline. Recording a CRM interaction or awarding loyalty is
|
|
58
|
+
> a separate, independent concern; don't route those through the comms trigger.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Roles by action
|
|
63
|
+
|
|
64
|
+
| Action | Roles | Fired when |
|
|
65
|
+
|--------|-------|-----------|
|
|
66
|
+
| `claim` / `claimProduct` | `claimer` | after the proof is committed to the ledger |
|
|
67
|
+
| `transfer` (directed) | `recipient`, `sender` | on initiation |
|
|
68
|
+
| `transfer` (`release: true`) | `owner` | on initiation |
|
|
69
|
+
| `acceptTransfer` | `recipient` (new owner), `sender` (previous owner) | after the ownership move completes |
|
|
70
|
+
| `cancelTransfer` | `owner` (canceller), `recipient` (earmarked, directed only) | after the transfer is cancelled |
|
|
71
|
+
|
|
72
|
+
Timing is deliberate: comms fire **only after the durable write** (the ledger
|
|
73
|
+
commit for a claim, the ownership move for an accept). A failed action sends
|
|
74
|
+
nothing; a comms failure never breaks the action (best-effort).
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Examples
|
|
79
|
+
|
|
80
|
+
Directed transfer — notify the buyer, confirm to the seller:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
await proof.transfer(collectionId, productId, proofId, {
|
|
84
|
+
toEmail: 'buyer@example.com',
|
|
85
|
+
comms: {
|
|
86
|
+
recipient: { templateId: 'transfer-incoming', props: { note: 'Enjoy the watch!' } },
|
|
87
|
+
sender: { templateId: 'transfer-sent' },
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Claim — confirm to the claimer:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
await proof.claim(collectionId, productId, proofId, {
|
|
96
|
+
data: { purchaseStore: 'Regent St' },
|
|
97
|
+
comms: {
|
|
98
|
+
claimer: { templateId: 'welcome-owner', channel: 'preferred' },
|
|
99
|
+
},
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Accept — completion confirmation to both parties:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
await proof.acceptTransfer(collectionId, productId, proofId, {
|
|
107
|
+
comms: {
|
|
108
|
+
recipient: { templateId: 'transfer-complete-owner' },
|
|
109
|
+
sender: { templateId: 'transfer-complete-previous' },
|
|
110
|
+
},
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Suppress comms for one role while still messaging another:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
comms: {
|
|
118
|
+
recipient: { templateId: 'transfer-incoming' },
|
|
119
|
+
sender: { notify: false }, // do the transfer, don't email the seller
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Omit `comms` entirely and nothing is sent — comms are **opt-in**.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Notes
|
|
128
|
+
|
|
129
|
+
- **Email-only recipients work.** A directed transfer to a `toEmail` that isn't a
|
|
130
|
+
contact yet resolves/creates a contact before sending.
|
|
131
|
+
- **Consent + suppression apply.** Transactional sends still respect the contact's
|
|
132
|
+
channel consent and suppression list; a template's `topic` governs this.
|
|
133
|
+
- **Delivery is logged** to comms history (with your `appId` and a `ref`), so sends
|
|
134
|
+
are auditable per contact.
|
|
135
|
+
|
|
136
|
+
See also [Proof Ownership Transfer](./proof-ownership-transfer.md),
|
|
137
|
+
[Proof Claiming Methods](./proof-claiming-methods.md), and [Comms](./comms.md).
|
|
@@ -15,7 +15,7 @@ reserves states for them.
|
|
|
15
15
|
|
|
16
16
|
| Mode | How | Who completes it |
|
|
17
17
|
|------|-----|------------------|
|
|
18
|
-
| **Directed** | Owner names a recipient (`toEmail` / `toUserId`). The proof is earmarked for them (`claimUserId`)
|
|
18
|
+
| **Directed** | Owner names a recipient (`toEmail` / `toUserId`). The proof is earmarked for them (`claimUserId`); you notify them with a comms trigger. | The named recipient calls `acceptTransfer`. |
|
|
19
19
|
| **Open release** | Owner marks the proof `claimable`. | Anyone claims it via the normal claim flow. |
|
|
20
20
|
|
|
21
21
|
Only the current owner (or a collection admin) can start a transfer. A proof can
|
|
@@ -41,10 +41,14 @@ authoritative and atomic:
|
|
|
41
41
|
```ts
|
|
42
42
|
import { proof } from '@proveanything/smartlinks'
|
|
43
43
|
|
|
44
|
-
// Directed — hand it to a named buyer (they must accept)
|
|
44
|
+
// Directed — hand it to a named buyer (they must accept). Notify each party with a
|
|
45
|
+
// comms trigger naming a template you authored (see Comms Triggers below).
|
|
45
46
|
const { transfer } = await proof.transfer(collectionId, productId, proofId, {
|
|
46
47
|
toEmail: 'buyer@example.com',
|
|
47
|
-
|
|
48
|
+
comms: {
|
|
49
|
+
recipient: { templateId: 'transfer-incoming', props: { note: 'Enjoy the watch!' } },
|
|
50
|
+
sender: { templateId: 'transfer-sent' },
|
|
51
|
+
},
|
|
48
52
|
})
|
|
49
53
|
|
|
50
54
|
// …or an open release — anyone can now claim it
|
|
@@ -60,12 +64,18 @@ await proof.cancelTransfer(collectionId, productId, proofId)
|
|
|
60
64
|
## Recipient flow (directed)
|
|
61
65
|
|
|
62
66
|
```ts
|
|
63
|
-
// Only the named recipient can accept — a third party is rejected.
|
|
64
|
-
|
|
67
|
+
// Only the named recipient can accept — a third party is rejected. Optionally
|
|
68
|
+
// send completion comms to both parties.
|
|
69
|
+
const { proof: mine } = await proof.acceptTransfer(collectionId, productId, proofId, {
|
|
70
|
+
comms: {
|
|
71
|
+
recipient: { templateId: 'transfer-complete-owner' }, // the new owner
|
|
72
|
+
sender: { templateId: 'transfer-complete-previous' }, // the previous owner
|
|
73
|
+
},
|
|
74
|
+
})
|
|
65
75
|
```
|
|
66
76
|
|
|
67
77
|
The recipient sees the pending transfer on their account (a pending claim) and via
|
|
68
|
-
the
|
|
78
|
+
the notification you sent. Until they accept, the proof still belongs to the seller.
|
|
69
79
|
|
|
70
80
|
---
|
|
71
81
|
|
|
@@ -101,14 +111,34 @@ the email link. Until they accept, the proof still belongs to the seller.
|
|
|
101
111
|
added, are dispute-protected and never auto-transfer by default).
|
|
102
112
|
- **Every state change is audited** as an append-only attestation on the proof.
|
|
103
113
|
|
|
114
|
+
## Comms
|
|
115
|
+
|
|
116
|
+
Every transfer action takes an optional `comms` map (role → comms trigger) that
|
|
117
|
+
sends a transactional message once the action's write is durable. Roles:
|
|
118
|
+
|
|
119
|
+
| Action | Roles |
|
|
120
|
+
|--------|-------|
|
|
121
|
+
| `transfer` (directed) | `recipient`, `sender` |
|
|
122
|
+
| `transfer` (`release: true`) | `owner` |
|
|
123
|
+
| `acceptTransfer` | `recipient` (new owner), `sender` (previous owner) |
|
|
124
|
+
| `cancelTransfer` | `owner`, `recipient` (directed only) |
|
|
125
|
+
|
|
126
|
+
You author each template; the server owns who each role resolves to and hydrates
|
|
127
|
+
`{{ proof }}` / `{{ product }}` / `{{ contact }}` / `{{ proofUrl }}`. Pass
|
|
128
|
+
`notify: false` on a role (or omit `comms`) to send nothing. Full detail:
|
|
129
|
+
[Comms Triggers](./proof-comms-triggers.md).
|
|
130
|
+
|
|
104
131
|
## API
|
|
105
132
|
|
|
106
133
|
| Method | Endpoint |
|
|
107
134
|
|--------|----------|
|
|
108
135
|
| `proof.transfer(c, p, id, opts)` | `POST …/proof/:id/transfer` |
|
|
109
|
-
| `proof.acceptTransfer(c, p, id)` | `POST …/proof/:id/transfer/accept` |
|
|
110
|
-
| `proof.cancelTransfer(c, p, id)` | `POST …/proof/:id/transfer/cancel` |
|
|
136
|
+
| `proof.acceptTransfer(c, p, id, opts?)` | `POST …/proof/:id/transfer/accept` |
|
|
137
|
+
| `proof.cancelTransfer(c, p, id, opts?)` | `POST …/proof/:id/transfer/cancel` |
|
|
111
138
|
| `proof.getTransfer(c, p, id)` | `GET …/proof/:id/transfer` |
|
|
112
139
|
|
|
113
|
-
|
|
140
|
+
`opts` on transfer/accept/cancel carries the `comms` map (and `notify`).
|
|
141
|
+
|
|
142
|
+
See also [Comms Triggers](./proof-comms-triggers.md),
|
|
143
|
+
[Proof Share Grants](./proof-share-grants.md) and
|
|
114
144
|
[Proof Claiming Methods](./proof-claiming-methods.md).
|
package/openapi.yaml
CHANGED
|
@@ -25717,6 +25717,14 @@ components:
|
|
|
25717
25717
|
type: object
|
|
25718
25718
|
additionalProperties:
|
|
25719
25719
|
$ref: "#/components/schemas/JsonValue"
|
|
25720
|
+
ProofClaimRequest:
|
|
25721
|
+
type: object
|
|
25722
|
+
properties:
|
|
25723
|
+
data:
|
|
25724
|
+
type: object
|
|
25725
|
+
additionalProperties: true
|
|
25726
|
+
comms:
|
|
25727
|
+
$ref: "#/components/schemas/CommsTriggerMap"
|
|
25720
25728
|
ProofFieldsConfig:
|
|
25721
25729
|
type: object
|
|
25722
25730
|
properties:
|
|
@@ -25844,9 +25852,34 @@ components:
|
|
|
25844
25852
|
- state
|
|
25845
25853
|
- createdAt
|
|
25846
25854
|
- updatedAt
|
|
25855
|
+
CommsTrigger:
|
|
25856
|
+
type: object
|
|
25857
|
+
properties:
|
|
25858
|
+
templateId:
|
|
25859
|
+
type: string
|
|
25860
|
+
channel:
|
|
25861
|
+
type: string
|
|
25862
|
+
enum:
|
|
25863
|
+
- preferred
|
|
25864
|
+
- email
|
|
25865
|
+
- sms
|
|
25866
|
+
- push
|
|
25867
|
+
- wallet
|
|
25868
|
+
- whatsapp
|
|
25869
|
+
props:
|
|
25870
|
+
type: object
|
|
25871
|
+
additionalProperties: true
|
|
25872
|
+
notify:
|
|
25873
|
+
type: boolean
|
|
25874
|
+
appId:
|
|
25875
|
+
type: string
|
|
25876
|
+
required:
|
|
25877
|
+
- templateId
|
|
25847
25878
|
TransferProofOptions:
|
|
25848
25879
|
type: object
|
|
25849
25880
|
properties:
|
|
25881
|
+
comms:
|
|
25882
|
+
$ref: "#/components/schemas/CommsTriggerMap"
|
|
25850
25883
|
toEmail:
|
|
25851
25884
|
type: string
|
|
25852
25885
|
toUserId:
|
|
@@ -25875,6 +25908,20 @@ components:
|
|
|
25875
25908
|
- ok
|
|
25876
25909
|
- mode
|
|
25877
25910
|
- transfer
|
|
25911
|
+
AcceptTransferOptions:
|
|
25912
|
+
type: object
|
|
25913
|
+
properties:
|
|
25914
|
+
comms:
|
|
25915
|
+
$ref: "#/components/schemas/CommsTriggerMap"
|
|
25916
|
+
notify:
|
|
25917
|
+
type: boolean
|
|
25918
|
+
CancelTransferOptions:
|
|
25919
|
+
type: object
|
|
25920
|
+
properties:
|
|
25921
|
+
comms:
|
|
25922
|
+
$ref: "#/components/schemas/CommsTriggerMap"
|
|
25923
|
+
notify:
|
|
25924
|
+
type: boolean
|
|
25878
25925
|
ProofTransferType:
|
|
25879
25926
|
type: string
|
|
25880
25927
|
enum:
|