@nuanu-ai/agentify-contracts 0.3.2

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.
@@ -0,0 +1,174 @@
1
+ /**
2
+ * One message on the merchant's worker stream.
3
+ *
4
+ * The stream carries three different things — an order, a question about a
5
+ * price, a notification about an order — and it carries them on one connection
6
+ * because the portal promises the merchant one subscription (ADR-0004 §2).
7
+ * Everything on it is therefore wrapped in the same envelope, whose marker says
8
+ * which of the three the wrapper is holding.
9
+ *
10
+ * Three decisions are worth arguing rather than leaving to be inferred.
11
+ *
12
+ * The payload sits under its own key instead of being spread into the
13
+ * envelope, and the reason is a collision that would be silent. An order
14
+ * already carries an `id`, and that identifier is its idempotency key — the
15
+ * thing a handler answers from on a redelivery. Spread flat, the envelope's own
16
+ * identifier and the order's would be one field, one of the two would win, and
17
+ * whichever lost would be lost without a word. Nesting is what keeps the
18
+ * message's identity and the order's identity from ever being the same string.
19
+ *
20
+ * The key is `payload` for every kind rather than `order`, `quote_request` and
21
+ * `order_event` in turn. Naming it after the kind would say the same thing
22
+ * twice, and two things that say the same thing can disagree — a message
23
+ * marked one way and keyed the other has no reading. With one key the marker is
24
+ * the only place that answers "what is this", and it is checked: the schema
25
+ * holds the payload to the document its kind names, so a price question inside
26
+ * an envelope marked as an order is refused at the boundary rather than handed
27
+ * to an order handler.
28
+ *
29
+ * The kinds are the names the contract registry already publishes for those
30
+ * three documents. That is not decoration: the reader furthest from us has the
31
+ * JSON Schema export and nothing else, and a kind invented separately would be
32
+ * a second vocabulary for documents the export names once. A test holds the two
33
+ * in step.
34
+ *
35
+ * What the envelope does not carry is a handle for acking, because the
36
+ * acknowledgement of each kind is something that already exists: an order is
37
+ * acked by whatever its handler answers — the goods, a refusal, or taking the
38
+ * order on, each against the order's own identifier, and the last of those
39
+ * acknowledges without closing anything — a price question by the reply against
40
+ * `price_id`, and an event by nothing at all. A handle here would be a fourth
41
+ * way to name a message that three surfaces already name.
42
+ *
43
+ * That third one is a gap and not a design, and it decides how far each kind
44
+ * travels. Nothing takes a drawn envelope back after a visibility window: an
45
+ * order arrives again because the order machine decided there should be another
46
+ * attempt, and an event, which no machine is watching for a reply, does not
47
+ * arrive again at all. So an order is delivered at least once and an event at
48
+ * most once, and a consumer that guards one of them the way it guards the other
49
+ * has the wrong guard on both.
50
+ *
51
+ * The merchant never sees any of this. The SDK hands a handler an order, not
52
+ * the envelope it arrived in (ADR-0004 §5); the envelope is the format between
53
+ * the gateway and the SDK, and it lives in this package because both of them
54
+ * read it and neither owns it.
55
+ */
56
+ import { z } from "zod";
57
+ /**
58
+ * What each kind of envelope is holding, by the name the marker uses.
59
+ *
60
+ * The union below is built from these, so the marker and the check behind it
61
+ * cannot come apart.
62
+ */
63
+ export declare const WORKER_ENVELOPE_PAYLOADS: Readonly<{
64
+ /** A purchase that is paid for as far as its mode requires, waiting to be delivered. */
65
+ readonly order: z.ZodObject<{
66
+ id: z.ZodString;
67
+ merchant_item_id: z.ZodString;
68
+ params: z.ZodRecord<z.ZodString, z.ZodUnknown>;
69
+ price: z.ZodObject<{
70
+ amount: z.ZodString;
71
+ currency: z.ZodString;
72
+ at: z.ZodISODateTime;
73
+ as_of: z.ZodISODateTime;
74
+ }, z.core.$strict>;
75
+ price_id: z.ZodOptional<z.ZodString>;
76
+ test: z.ZodBoolean;
77
+ }, z.core.$strict>;
78
+ /** "How much is this and is it there", asked before a sale goes through. */
79
+ readonly quote_request: z.ZodObject<{
80
+ merchant_item_id: z.ZodString;
81
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
82
+ price_id: z.ZodString;
83
+ purpose: z.ZodEnum<{
84
+ purchase: "purchase";
85
+ poll: "poll";
86
+ }>;
87
+ expires_at: z.ZodISODateTime;
88
+ }, z.core.$strict>;
89
+ /** Something that happened to an order without the merchant doing anything. */
90
+ readonly order_event: z.ZodDiscriminatedUnion<[z.ZodObject<{
91
+ type: z.ZodLiteral<"order.refund_due">;
92
+ order_id: z.ZodString;
93
+ at: z.ZodISODateTime;
94
+ price: z.ZodObject<{
95
+ amount: z.ZodString;
96
+ currency: z.ZodString;
97
+ }, z.core.$strict>;
98
+ reason: z.ZodEnum<{
99
+ refused: "refused";
100
+ deadline_passed: "deadline_passed";
101
+ merchant_left: "merchant_left";
102
+ }>;
103
+ }, z.core.$strict>, z.ZodObject<{
104
+ type: z.ZodLiteral<"order.unpaid_after_confirmation">;
105
+ order_id: z.ZodString;
106
+ at: z.ZodISODateTime;
107
+ }, z.core.$strict>, z.ZodObject<{
108
+ type: z.ZodLiteral<"order.payment_failed_after_delivery">;
109
+ order_id: z.ZodString;
110
+ at: z.ZodISODateTime;
111
+ }, z.core.$strict>], "type">;
112
+ }>;
113
+ /** The marker one envelope carries, naming what its payload is. */
114
+ export type WorkerEnvelopeKind = keyof typeof WORKER_ENVELOPE_PAYLOADS;
115
+ export declare const WORKER_ENVELOPE_KINDS: readonly WorkerEnvelopeKind[];
116
+ export declare const WorkerEnvelopeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
117
+ kind: z.ZodLiteral<"order">;
118
+ id: z.ZodString;
119
+ sent_at: z.ZodISODateTime;
120
+ payload: z.ZodObject<{
121
+ id: z.ZodString;
122
+ merchant_item_id: z.ZodString;
123
+ params: z.ZodRecord<z.ZodString, z.ZodUnknown>;
124
+ price: z.ZodObject<{
125
+ amount: z.ZodString;
126
+ currency: z.ZodString;
127
+ at: z.ZodISODateTime;
128
+ as_of: z.ZodISODateTime;
129
+ }, z.core.$strict>;
130
+ price_id: z.ZodOptional<z.ZodString>;
131
+ test: z.ZodBoolean;
132
+ }, z.core.$strict>;
133
+ }, z.core.$strict>, z.ZodObject<{
134
+ kind: z.ZodLiteral<"quote_request">;
135
+ id: z.ZodString;
136
+ sent_at: z.ZodISODateTime;
137
+ payload: z.ZodObject<{
138
+ merchant_item_id: z.ZodString;
139
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
140
+ price_id: z.ZodString;
141
+ purpose: z.ZodEnum<{
142
+ purchase: "purchase";
143
+ poll: "poll";
144
+ }>;
145
+ expires_at: z.ZodISODateTime;
146
+ }, z.core.$strict>;
147
+ }, z.core.$strict>, z.ZodObject<{
148
+ kind: z.ZodLiteral<"order_event">;
149
+ id: z.ZodString;
150
+ sent_at: z.ZodISODateTime;
151
+ payload: z.ZodDiscriminatedUnion<[z.ZodObject<{
152
+ type: z.ZodLiteral<"order.refund_due">;
153
+ order_id: z.ZodString;
154
+ at: z.ZodISODateTime;
155
+ price: z.ZodObject<{
156
+ amount: z.ZodString;
157
+ currency: z.ZodString;
158
+ }, z.core.$strict>;
159
+ reason: z.ZodEnum<{
160
+ refused: "refused";
161
+ deadline_passed: "deadline_passed";
162
+ merchant_left: "merchant_left";
163
+ }>;
164
+ }, z.core.$strict>, z.ZodObject<{
165
+ type: z.ZodLiteral<"order.unpaid_after_confirmation">;
166
+ order_id: z.ZodString;
167
+ at: z.ZodISODateTime;
168
+ }, z.core.$strict>, z.ZodObject<{
169
+ type: z.ZodLiteral<"order.payment_failed_after_delivery">;
170
+ order_id: z.ZodString;
171
+ at: z.ZodISODateTime;
172
+ }, z.core.$strict>], "type">;
173
+ }, z.core.$strict>], "kind">;
174
+ export type WorkerEnvelope = z.infer<typeof WorkerEnvelopeSchema>;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * One message on the merchant's worker stream.
3
+ *
4
+ * The stream carries three different things — an order, a question about a
5
+ * price, a notification about an order — and it carries them on one connection
6
+ * because the portal promises the merchant one subscription (ADR-0004 §2).
7
+ * Everything on it is therefore wrapped in the same envelope, whose marker says
8
+ * which of the three the wrapper is holding.
9
+ *
10
+ * Three decisions are worth arguing rather than leaving to be inferred.
11
+ *
12
+ * The payload sits under its own key instead of being spread into the
13
+ * envelope, and the reason is a collision that would be silent. An order
14
+ * already carries an `id`, and that identifier is its idempotency key — the
15
+ * thing a handler answers from on a redelivery. Spread flat, the envelope's own
16
+ * identifier and the order's would be one field, one of the two would win, and
17
+ * whichever lost would be lost without a word. Nesting is what keeps the
18
+ * message's identity and the order's identity from ever being the same string.
19
+ *
20
+ * The key is `payload` for every kind rather than `order`, `quote_request` and
21
+ * `order_event` in turn. Naming it after the kind would say the same thing
22
+ * twice, and two things that say the same thing can disagree — a message
23
+ * marked one way and keyed the other has no reading. With one key the marker is
24
+ * the only place that answers "what is this", and it is checked: the schema
25
+ * holds the payload to the document its kind names, so a price question inside
26
+ * an envelope marked as an order is refused at the boundary rather than handed
27
+ * to an order handler.
28
+ *
29
+ * The kinds are the names the contract registry already publishes for those
30
+ * three documents. That is not decoration: the reader furthest from us has the
31
+ * JSON Schema export and nothing else, and a kind invented separately would be
32
+ * a second vocabulary for documents the export names once. A test holds the two
33
+ * in step.
34
+ *
35
+ * What the envelope does not carry is a handle for acking, because the
36
+ * acknowledgement of each kind is something that already exists: an order is
37
+ * acked by whatever its handler answers — the goods, a refusal, or taking the
38
+ * order on, each against the order's own identifier, and the last of those
39
+ * acknowledges without closing anything — a price question by the reply against
40
+ * `price_id`, and an event by nothing at all. A handle here would be a fourth
41
+ * way to name a message that three surfaces already name.
42
+ *
43
+ * That third one is a gap and not a design, and it decides how far each kind
44
+ * travels. Nothing takes a drawn envelope back after a visibility window: an
45
+ * order arrives again because the order machine decided there should be another
46
+ * attempt, and an event, which no machine is watching for a reply, does not
47
+ * arrive again at all. So an order is delivered at least once and an event at
48
+ * most once, and a consumer that guards one of them the way it guards the other
49
+ * has the wrong guard on both.
50
+ *
51
+ * The merchant never sees any of this. The SDK hands a handler an order, not
52
+ * the envelope it arrived in (ADR-0004 §5); the envelope is the format between
53
+ * the gateway and the SDK, and it lives in this package because both of them
54
+ * read it and neither owns it.
55
+ */
56
+ import { z } from "zod";
57
+ import { OrderEventSchema } from "./events.js";
58
+ import { OrderSchema } from "./order.js";
59
+ import { IdentifierSchema, TimestampSchema } from "./primitives.js";
60
+ import { QuoteRequestSchema } from "./quote.js";
61
+ /**
62
+ * What each kind of envelope is holding, by the name the marker uses.
63
+ *
64
+ * The union below is built from these, so the marker and the check behind it
65
+ * cannot come apart.
66
+ */
67
+ export const WORKER_ENVELOPE_PAYLOADS = Object.freeze({
68
+ /** A purchase that is paid for as far as its mode requires, waiting to be delivered. */
69
+ order: OrderSchema,
70
+ /** "How much is this and is it there", asked before a sale goes through. */
71
+ quote_request: QuoteRequestSchema,
72
+ /** Something that happened to an order without the merchant doing anything. */
73
+ order_event: OrderEventSchema,
74
+ });
75
+ export const WORKER_ENVELOPE_KINDS = Object.freeze(Object.keys(WORKER_ENVELOPE_PAYLOADS));
76
+ /**
77
+ * The fields every envelope carries, whatever it is holding.
78
+ *
79
+ * `id` names this message and `sent_at` names when it went out. What the pair is
80
+ * not is a way to recognise a repeat, and reading it as one costs a merchant
81
+ * goods: an order the gateway decides to send again is wrapped in a fresh
82
+ * envelope with a fresh `id`, so the only thing two attempts at one order have
83
+ * in common is the order they carry. The gateway does put an envelope back on the stream with its `id`
84
+ * untouched, but only on the paths where it drew that envelope and then handed
85
+ * it to nobody — so an identifier a worker has actually seen never comes round a
86
+ * second time. What holds still across a repeat is the order's own identifier
87
+ * inside the payload, which is what its handler answers from and what the portal
88
+ * tells a merchant to key on. An event needs neither, having no repeat to
89
+ * recognise.
90
+ */
91
+ const envelopeOf = (kind, payload) => z.strictObject({
92
+ kind: z.literal(kind),
93
+ id: IdentifierSchema,
94
+ sent_at: TimestampSchema,
95
+ payload,
96
+ });
97
+ export const WorkerEnvelopeSchema = z.discriminatedUnion("kind", [
98
+ envelopeOf("order", WORKER_ENVELOPE_PAYLOADS.order),
99
+ envelopeOf("quote_request", WORKER_ENVELOPE_PAYLOADS.quote_request),
100
+ envelopeOf("order_event", WORKER_ENVELOPE_PAYLOADS.order_event),
101
+ ]);
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The things that happen to an order without the merchant doing anything, and
3
+ * that they would otherwise only find by reconciling records by hand.
4
+ *
5
+ * They arrive on the same subscription as the orders and want no answer: an
6
+ * event notifies, it does not ask for work. The catalog is a minimum rather
7
+ * than a closed list, and it grows by a shape being added here — never by a
8
+ * sender inventing a name, because a consumer switching on the type has to be
9
+ * able to read an unknown one as a version mismatch rather than a typo.
10
+ *
11
+ * The names are prefixed by what they are about. Everything here is about an
12
+ * order; something that is not will not have to fight for a name.
13
+ */
14
+ import { z } from "zod";
15
+ export declare const ORDER_EVENT_TYPES: Readonly<{
16
+ /** The money moved and the delivery did not happen. */
17
+ readonly REFUND_DUE: "order.refund_due";
18
+ /** The merchant said they would deliver and the agent never paid. */
19
+ readonly UNPAID_AFTER_CONFIRMATION: "order.unpaid_after_confirmation";
20
+ /** The merchant delivered synchronously and the payment did not execute. */
21
+ readonly PAYMENT_FAILED_AFTER_DELIVERY: "order.payment_failed_after_delivery";
22
+ }>;
23
+ /**
24
+ * How an order came to owe a refund: the merchant refused after the money had
25
+ * moved, their delivery deadline passed, or they left and their open orders
26
+ * closed with them.
27
+ */
28
+ export declare const RefundDueReasonSchema: z.ZodEnum<{
29
+ refused: "refused";
30
+ deadline_passed: "deadline_passed";
31
+ merchant_left: "merchant_left";
32
+ }>;
33
+ export declare const OrderEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
34
+ type: z.ZodLiteral<"order.refund_due">;
35
+ order_id: z.ZodString;
36
+ at: z.ZodISODateTime;
37
+ price: z.ZodObject<{
38
+ amount: z.ZodString;
39
+ currency: z.ZodString;
40
+ }, z.core.$strict>;
41
+ reason: z.ZodEnum<{
42
+ refused: "refused";
43
+ deadline_passed: "deadline_passed";
44
+ merchant_left: "merchant_left";
45
+ }>;
46
+ }, z.core.$strict>, z.ZodObject<{
47
+ type: z.ZodLiteral<"order.unpaid_after_confirmation">;
48
+ order_id: z.ZodString;
49
+ at: z.ZodISODateTime;
50
+ }, z.core.$strict>, z.ZodObject<{
51
+ type: z.ZodLiteral<"order.payment_failed_after_delivery">;
52
+ order_id: z.ZodString;
53
+ at: z.ZodISODateTime;
54
+ }, z.core.$strict>], "type">;
55
+ export type RefundDueReason = z.infer<typeof RefundDueReasonSchema>;
56
+ export type OrderEvent = z.infer<typeof OrderEventSchema>;
package/dist/events.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The things that happen to an order without the merchant doing anything, and
3
+ * that they would otherwise only find by reconciling records by hand.
4
+ *
5
+ * They arrive on the same subscription as the orders and want no answer: an
6
+ * event notifies, it does not ask for work. The catalog is a minimum rather
7
+ * than a closed list, and it grows by a shape being added here — never by a
8
+ * sender inventing a name, because a consumer switching on the type has to be
9
+ * able to read an unknown one as a version mismatch rather than a typo.
10
+ *
11
+ * The names are prefixed by what they are about. Everything here is about an
12
+ * order; something that is not will not have to fight for a name.
13
+ */
14
+ import { z } from "zod";
15
+ import { IdentifierSchema, MoneySchema, TimestampSchema } from "./primitives.js";
16
+ export const ORDER_EVENT_TYPES = Object.freeze({
17
+ /** The money moved and the delivery did not happen. */
18
+ REFUND_DUE: "order.refund_due",
19
+ /** The merchant said they would deliver and the agent never paid. */
20
+ UNPAID_AFTER_CONFIRMATION: "order.unpaid_after_confirmation",
21
+ /** The merchant delivered synchronously and the payment did not execute. */
22
+ PAYMENT_FAILED_AFTER_DELIVERY: "order.payment_failed_after_delivery",
23
+ });
24
+ /**
25
+ * How an order came to owe a refund: the merchant refused after the money had
26
+ * moved, their delivery deadline passed, or they left and their open orders
27
+ * closed with them.
28
+ */
29
+ export const RefundDueReasonSchema = z.enum(["refused", "deadline_passed", "merchant_left"]);
30
+ export const OrderEventSchema = z.discriminatedUnion("type", [
31
+ z.strictObject({
32
+ type: z.literal(ORDER_EVENT_TYPES.REFUND_DUE),
33
+ order_id: IdentifierSchema,
34
+ at: TimestampSchema,
35
+ /**
36
+ * The sum owed, which is the price the sale went through at. The payment
37
+ * went from the buyer straight to the merchant's wallet and never passed
38
+ * through us, so sending it back is theirs to do — and an event naming
39
+ * only the order would leave them looking the sum up.
40
+ */
41
+ price: MoneySchema,
42
+ reason: RefundDueReasonSchema,
43
+ }),
44
+ z.strictObject({
45
+ type: z.literal(ORDER_EVENT_TYPES.UNPAID_AFTER_CONFIRMATION),
46
+ order_id: IdentifierSchema,
47
+ at: TimestampSchema,
48
+ }),
49
+ z.strictObject({
50
+ type: z.literal(ORDER_EVENT_TYPES.PAYMENT_FAILED_AFTER_DELIVERY),
51
+ order_id: IdentifierSchema,
52
+ at: TimestampSchema,
53
+ }),
54
+ ]);
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The address money can be sent to on an EVM chain, and the one thing that can
3
+ * be checked about one before it is used.
4
+ *
5
+ * An address is twenty bytes and nothing about those bytes is redundant, so a
6
+ * character lost or mistyped in one produces another perfectly valid address —
7
+ * belonging to nobody, or to somebody else, and a payment to it is gone. EIP-55
8
+ * is the only guard anybody has against that: the capital letters in the
9
+ * spelling a wallet shows are not decoration, they are a checksum over the
10
+ * address itself, and a character that is wrong stops the capitals agreeing
11
+ * with the digits. Checked here, a mistyped address is a sentence a merchant
12
+ * reads while they still have the right one in front of them; not checked, it
13
+ * is a stranger's wallet filling up with somebody's sales.
14
+ *
15
+ * Two spellings are accepted and no others. All lower case is the spelling that
16
+ * carries no checksum at all, and it is what a block explorer prints and what
17
+ * half the tooling in this world hands somebody, so refusing it would refuse
18
+ * addresses that are perfectly good. The other is the exact output of the
19
+ * checksum, which is what a wallet puts on the clipboard. Anything between the
20
+ * two is a string that has been through something: a case-mangling copy, a
21
+ * document that shouted it, or a keystroke in the wrong place. The refusal says
22
+ * so and says what to do instead, because "invalid address" leaves a person
23
+ * staring at forty characters that look fine.
24
+ *
25
+ * Of the two, the wallet's is the canon: the door takes both spellings and
26
+ * everything behind it holds one (ADR-0017), and `checksummedAddressOf` below
27
+ * is what writes it. The reason is about the person rather than about the
28
+ * storage, and it is written where that function is.
29
+ *
30
+ * The hash is written out below rather than taken from a package, and that is a
31
+ * decision rather than an oversight. This package's runtime dependency tree is
32
+ * `zod` and nothing else, and it is the whole of what a merchant installs with
33
+ * the SDK — ADR-0003 §8 makes every addition to it a decision of its own, and
34
+ * that decision is not this file's to take. What is written out is therefore
35
+ * kept as small as the job allows: twenty-four rounds of a fixed permutation
36
+ * over one block, with no configuration and no second use, and it is pinned by
37
+ * the vectors the standard publishes, so an implementation that is wrong
38
+ * anywhere fails against somebody else's arithmetic rather than agreeing with
39
+ * itself. The cost is named too: this is hand-written cryptographic code in a
40
+ * published package, and the trigger to replace it with an audited library is
41
+ * the first other thing in this package that needs a hash.
42
+ */
43
+ import { z } from "zod";
44
+ /**
45
+ * One address written the way the wallet it came out of displays it: the
46
+ * canonical form, and the one anything behind the door keeps.
47
+ *
48
+ * The door takes two spellings and everything behind it holds one (ADR-0017),
49
+ * and this is the one, because it is the one a person recognises. A merchant
50
+ * pastes forty characters out of their wallet and then reads them back on a
51
+ * settings screen; handed the same address in lower case they cannot tell it
52
+ * from a different address without comparing character by character, and the
53
+ * one field in this system where that matters is the field money is sent to.
54
+ * Lower case would be the easier canon to compute and the harder one to check
55
+ * by eye, and the eye is the only thing checking.
56
+ *
57
+ * It refuses what the schema refuses rather than quietly repairing it. Given a
58
+ * mixed-case address whose letters disagree with its digits, capitalising it
59
+ * afresh would produce a perfectly well-formed address that nobody typed —
60
+ * which is the exact failure the checksum exists to catch, arrived at by the
61
+ * thing meant to enforce it.
62
+ *
63
+ * Given an address that is already in this form it answers with the same
64
+ * string, so writing one down twice writes the same thing twice.
65
+ */
66
+ export declare function checksummedAddressOf(address: string): string;
67
+ /**
68
+ * An address on an EVM chain, in one of the two spellings anybody writes one
69
+ * in.
70
+ *
71
+ * It carries no chain of its own. The same twenty bytes are an address on every
72
+ * EVM network, and which network a payment is made on is settled by the payment
73
+ * requirements rather than by the address inside them; a schema that claimed
74
+ * otherwise would be claiming knowledge this package does not have.
75
+ */
76
+ export declare const EvmAddressSchema: z.ZodString;
77
+ export type EvmAddress = z.infer<typeof EvmAddressSchema>;