remesso-sdk 0.1.0 → 0.2.0

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/README.md CHANGED
@@ -210,6 +210,40 @@ await agent.requests.pay(requestId); // its reference goes in the memo, so the r
210
210
  (`privateKeyToAccount`). The agent's key pays its own gas: send it a little
211
211
  CELO.
212
212
 
213
+ ## Builder codes
214
+
215
+ Celo credits on-chain volume to the app that caused it, by the ERC-8021 code
216
+ on each transaction
217
+ ([attribution tags](https://github.com/celo-org/attribution-tags)). Pass your
218
+ own, and every transaction this client sends carries it after Remesso's.
219
+ Remesso's executor then carries it on every run of each schedule the client
220
+ creates, so the volume a schedule moves counts for you too, for as long as it
221
+ runs.
222
+
223
+ ```ts
224
+ import { createRemesso } from "remesso-sdk";
225
+ import { privateKeyToAccount } from "viem/accounts";
226
+
227
+ const payer = createRemesso({
228
+ account: privateKeyToAccount(process.env.PAYER_KEY as `0x${string}`),
229
+ builderCodes: ["your_code"],
230
+ });
231
+ const s = await payer.schedules.create({ to: "0xRecipient", amount: "5", token: "USDT", every: "month", runs: 12 });
232
+
233
+ // A minute later, what Remesso read from the transaction that created it:
234
+ const { events } = await payer.events.list({ role: "from", kinds: ["schedule_created"] });
235
+ console.log(events.find((e) => e.refId === s.id.toString())?.details?.builderCodes); // ["your_code"]
236
+ ```
237
+
238
+ - Up to three codes, each 1 to 32 of `a-z`, `0-9` and `_`. The SDK refuses
239
+ anything else before signing, by the rules the executor applies.
240
+ - Without the SDK, tag your own `createSchedule` transaction with
241
+ `toDataSuffix` or `withAttribution` from `@celo/attribution-tags`. Only
242
+ Schema 0, the one they write, is read. A schedule created through an
243
+ ERC-4337 bundle isn't read yet: the codes come from the outer transaction.
244
+ - The suffix sits after the calldata, where the EVM ignores it. A code
245
+ changes who is credited, never what a payment does.
246
+
213
247
  ## What it costs
214
248
 
215
249
  - **0.25% of each scheduled payment**, taken from it and rounded up: a 1 USDT
@@ -241,6 +275,7 @@ await agent.requests.pay(requestId); // its reference goes in the memo, so the r
241
275
  | `store` | Where agent payments are recorded so a retry pays once. Default: memory. |
242
276
  | `fetchText` | Reads an https or ipfs registration file for `agents.find`: pass `safeFetchText`. |
243
277
  | `attribution` | Tag transactions for ERC-8021 attribution. Default true. |
278
+ | `builderCodes` | Your own ERC-8021 codes, up to three: on every transaction this client sends, and on every run of the schedules it creates (see Builder codes). |
244
279
 
245
280
  The client:
246
281
 
@@ -296,6 +331,7 @@ The client:
296
331
  | `CHAIN_ID`, `EXECUTOR`, `ALLOWANCE`, `REGISTRY`, `CURRENT_EXECUTOR`, `FEE_BPS` | The chain, the contracts, Remesso's trigger sentinel and the commission. |
297
332
  | `executorAbi`, `allowanceAbi`, `erc20Abi`, `registryAbi` | ABIs. |
298
333
  | `ATTRIBUTION` | The ERC-8021 suffix the SDK adds to transactions. |
334
+ | `attributionFor(builderCodes)` | That suffix with your codes after ours, checked by the executor's rules: for a transaction you send yourself. |
299
335
  | `CADENCES` | `week`, `fortnight`, `month` and `quarter`, in seconds. |
300
336
  | `RemessoError` | Every refusal; see the codes below. |
301
337
 
@@ -1 +1,3 @@
1
+ import type { Hex } from "viem";
1
2
  export declare const ATTRIBUTION: "0x72656d6573736f2c63656c6f5f356364333563613535626166190080218021802180218021802180218021";
3
+ export declare function attributionFor(builderCodes?: readonly string[], ours?: boolean): Hex | undefined;
@@ -1,8 +1,29 @@
1
- /// ERC-8021 attribution, after the calldata where the EVM ignores it:
2
- /// Remesso's code plus the one Loops issued for Agents on Open Rails. It
3
- /// credits the app that caused a transaction and changes nothing it does.
4
- /// The same suffix the Remesso app and the plugin wallet put on every write.
5
- /// Byte-identical to `toDataSuffix(["remesso", "celo_5cd35ca55baf"])` from
6
- /// `@celo/attribution-tags`; `test/attribution.test.ts` pins it to the
7
- /// backend's own encoder.
1
+ import { RemessoError } from "./errors.js";
2
+ import { CODE, MAX_INTEGRATOR_CODES, OWN_CODES, suffixFor } from "./vendor/erc8021.js";
8
3
  export const ATTRIBUTION = "0x72656d6573736f2c63656c6f5f356364333563613535626166190080218021802180218021802180218021";
4
+ /// What `ATTRIBUTION` carries.
5
+ const OURS = ["remesso", "celo_5cd35ca55baf"];
6
+ /// The suffix with your own builder codes after ours. Put on the transaction
7
+ /// that creates a schedule, they ride on every run of it too: Remesso's
8
+ /// executor reads them and tags each run with them, so Celo counts the volume
9
+ /// for you as well as for us.
10
+ ///
11
+ /// Checked by the rules the backend applies (`vendor/erc8021.ts`), so a code
12
+ /// accepted here is one the runs carry: Celo's charset, none of Remesso's own,
13
+ /// no repeats, at most three. `ours: false` leaves our codes out.
14
+ export function attributionFor(builderCodes = [], ours = true) {
15
+ for (const code of builderCodes) {
16
+ if (!CODE.test(code)) {
17
+ throw new RemessoError("invalid", `builder code ${JSON.stringify(code)}: 1 to 32 of a-z, 0-9 and _, as Celo's attribution codes are`);
18
+ }
19
+ if (OWN_CODES.includes(code))
20
+ throw new RemessoError("invalid", `${code} is Remesso's own code: pass yours`);
21
+ }
22
+ if (new Set(builderCodes).size !== builderCodes.length)
23
+ throw new RemessoError("invalid", "a builder code is repeated");
24
+ if (builderCodes.length > MAX_INTEGRATOR_CODES) {
25
+ throw new RemessoError("invalid", `at most ${MAX_INTEGRATOR_CODES} builder codes ride on a schedule's runs`);
26
+ }
27
+ const codes = [...(ours ? OURS : []), ...builderCodes];
28
+ return codes.length ? suffixFor(codes) : undefined;
29
+ }
package/dist/client.d.ts CHANGED
@@ -26,6 +26,14 @@ export type RemessoOptions = {
26
26
  fetchText?: (url: string) => Promise<string>;
27
27
  /** Tag transactions for ERC-8021 attribution. Default true. */
28
28
  attribution?: boolean;
29
+ /**
30
+ * Your own ERC-8021 builder codes, up to three, carried after Remesso's on
31
+ * every transaction this client sends. Remesso's executor also carries them
32
+ * on every run of the schedules you create, so Celo counts that volume for
33
+ * you. Throws on a code outside Celo's charset, one of Remesso's own, a
34
+ * repeat, or more than three.
35
+ */
36
+ builderCodes?: string[];
29
37
  /** For tests: another Remesso backend, and the app origin links point at. */
30
38
  functionsUrl?: string;
31
39
  appOrigin?: string;
package/dist/client.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { createPublicClient, createWalletClient, http } from "viem";
3
3
  import { celo } from "viem/chains";
4
4
  import { makeAgents } from "./agents.js";
5
- import { ATTRIBUTION } from "./attribution.js";
5
+ import { attributionFor } from "./attribution.js";
6
6
  import { makeCtx } from "./context.js";
7
7
  import { deployment } from "./deployment.js";
8
8
  import { makeEvents } from "./events.js";
@@ -31,7 +31,7 @@ export function createRemesso(opts = {}) {
31
31
  },
32
32
  appOrigin,
33
33
  fetch: opts.fetch ?? globalThis.fetch.bind(globalThis),
34
- dataSuffix: opts.attribution === false ? undefined : ATTRIBUTION,
34
+ dataSuffix: attributionFor(opts.builderCodes, opts.attribution !== false),
35
35
  store: opts.store ?? memoryStore(),
36
36
  fetchText: opts.fetchText,
37
37
  });
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export { RemessoError, type RemessoErrorCode } from "./errors.js";
3
3
  export { ALLOWANCE, CHAIN_ID, CURRENT_EXECUTOR, deployment, EXECUTOR, FEE_BPS, findToken, formatAmount, REGISTRY, runLimits, type Token, token, toUnits, } from "./deployment.js";
4
4
  export { feeOf, grossFor, netOf } from "./vendor/fee.js";
5
5
  export { allowanceAbi, erc20Abi, executorAbi, registryAbi } from "./abi.js";
6
- export { ATTRIBUTION } from "./attribution.js";
6
+ export { ATTRIBUTION, attributionFor } from "./attribution.js";
7
7
  export { CADENCES, type Cadence, type CreatedSchedule, type CreateScheduleInput, type Schedule } from "./schedules.js";
8
8
  export { memo32, type PayInput, type PaymentResult } from "./payments.js";
9
9
  export type { CreatePolicyInput, Policy } from "./policies.js";
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ export { RemessoError } from "./errors.js";
7
7
  export { ALLOWANCE, CHAIN_ID, CURRENT_EXECUTOR, deployment, EXECUTOR, FEE_BPS, findToken, formatAmount, REGISTRY, runLimits, token, toUnits, } from "./deployment.js";
8
8
  export { feeOf, grossFor, netOf } from "./vendor/fee.js";
9
9
  export { allowanceAbi, erc20Abi, executorAbi, registryAbi } from "./abi.js";
10
- export { ATTRIBUTION } from "./attribution.js";
10
+ export { ATTRIBUTION, attributionFor } from "./attribution.js";
11
11
  export { CADENCES } from "./schedules.js";
12
12
  export { memo32 } from "./payments.js";
13
13
  export { parseWebhook, verifyWebhook, webhookHandler, } from "./webhooks.js";
@@ -0,0 +1,9 @@
1
+ export declare const MARKER = "80218021802180218021802180218021";
2
+ export declare const CODE: RegExp;
3
+ export declare const OWN_CODES: readonly string[];
4
+ export declare const MAX_INTEGRATOR_CODES = 3;
5
+ export declare function suffixFor(codes: string | readonly string[]): `0x${string}` | undefined;
6
+ export declare function parseSuffix(input: string): {
7
+ codes: string[];
8
+ } | null;
9
+ export declare function integratorCodes(codes: readonly string[], own?: readonly string[]): string[];
@@ -0,0 +1,82 @@
1
+ // Copied from supabase/functions/_shared/erc8021.ts by scripts/build-sdk.ts. Edit the original and re-run;
2
+ // `build-sdk.ts --check` fails while the two differ.
3
+ /// ERC-8021 Schema 0: writing a suffix, reading one back, and which of the
4
+ /// codes an integrator's transaction carries ride on the runs that follow it.
5
+ ///
6
+ /// Pure, with no environment, so `remesso-sdk` carries an exact copy
7
+ /// (`scripts/build-sdk.ts`): a builder code the SDK accepts is one the backend
8
+ /// keeps. `attribution.ts` adds Remesso's own configured codes on top.
9
+ /// `attribution.test.ts` pins both directions to `@celo/attribution-tags`.
10
+ /// `[code:N][length:1][schema:1][marker:16]`
11
+ export const MARKER = "80218021802180218021802180218021";
12
+ const SCHEMA_0 = "00";
13
+ /// What Celo's SDK accepts in `toDataSuffix`.
14
+ export const CODE = /^[a-z0-9_]{1,32}$/;
15
+ /// Remesso's own codes, whether or not they're carried today. `remesso-sdk`
16
+ /// 0.1.0 writes both on every transaction, so a schedule created with it
17
+ /// after the event code leaves `ATTRIBUTION_CODE` must not hand that code
18
+ /// back to our runs as if it were an integrator's.
19
+ export const OWN_CODES = ["remesso", "celo_5cd35ca55baf"];
20
+ /// How many of an integrator's codes ride on each run. Each costs calldata
21
+ /// gas on every run, and three cover an app, a wallet and one partner.
22
+ export const MAX_INTEGRATOR_CODES = 3;
23
+ /// The suffix for one code or several, or `undefined` if none is usable.
24
+ ///
25
+ /// Never throws: a malformed tag must not be the reason a remittance fails to
26
+ /// go out. A bad code is dropped and the rest still tag — a typo in one must
27
+ /// not cost the other its credit, since an untagged transaction stays
28
+ /// untagged forever. A comma inside a single code is refused rather than read
29
+ /// as a list; a list is passed as an array.
30
+ export function suffixFor(codes) {
31
+ const list = (typeof codes === "string" ? [codes] : codes).filter((code) => {
32
+ if (CODE.test(code))
33
+ return true;
34
+ console.error("attribution code must be 1-32 of [a-z0-9_]:", code);
35
+ return false;
36
+ });
37
+ if (list.length === 0)
38
+ return undefined;
39
+ const field = list.join(",");
40
+ // The length is one byte.
41
+ if (field.length > 255)
42
+ return undefined;
43
+ const hex = [...new TextEncoder().encode(field)]
44
+ .map((b) => b.toString(16).padStart(2, "0"))
45
+ .join("");
46
+ const length = field.length.toString(16).padStart(2, "0");
47
+ return `0x${hex}${length}${SCHEMA_0}${MARKER}`;
48
+ }
49
+ /// The codes in a transaction's Schema 0 suffix, read from the end the way
50
+ /// Celo's `fromDataSuffix` reads them: the marker, the schema byte, the length
51
+ /// byte, then that many bytes of comma-joined codes, returned as written.
52
+ ///
53
+ /// Null when there is no suffix, when it is another schema (Schema 2's roles
54
+ /// aren't read), or when the length runs past the start of the input. ox
55
+ /// reads whatever bytes are there in that last case; nothing honest writes it.
56
+ export function parseSuffix(input) {
57
+ const hex = input.toLowerCase().replace(/^0x/, "");
58
+ if (hex.length % 2 !== 0 || hex.length < 36 || !/^[0-9a-f]*$/.test(hex))
59
+ return null;
60
+ if (hex.slice(-32) !== MARKER || hex.slice(-34, -32) !== SCHEMA_0)
61
+ return null;
62
+ const length = parseInt(hex.slice(-36, -34), 16);
63
+ if (length === 0 || hex.length < 36 + length * 2)
64
+ return null;
65
+ const field = hex.slice(-36 - length * 2, -36);
66
+ const bytes = Uint8Array.from(field.match(/../g), (b) => parseInt(b, 16));
67
+ return { codes: new TextDecoder().decode(bytes).split(",") };
68
+ }
69
+ /// Which of a creating transaction's codes ride on a schedule's runs: Celo's
70
+ /// charset, none of Remesso's own (they're carried anyway), no repeats, and
71
+ /// the first three in the order written.
72
+ export function integratorCodes(codes, own = OWN_CODES) {
73
+ const out = [];
74
+ for (const code of codes) {
75
+ if (!CODE.test(code) || own.includes(code) || out.includes(code))
76
+ continue;
77
+ out.push(code);
78
+ if (out.length === MAX_INTEGRATOR_CODES)
79
+ break;
80
+ }
81
+ return out;
82
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remesso-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Recurring and agent payments on Celo: schedules a payer signs once, agent payments capped on-chain by their human, payment requests, payment events and signed webhooks. TypeScript, on viem.",
5
5
  "type": "module",
6
6
  "exports": {