@catena/sdk 0.1.0 → 0.4.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.
@@ -92,7 +92,7 @@ function p256SigningKey(point, privateKeyHex) {
92
92
  }
93
93
  //#endregion
94
94
  //#region package.json
95
- var version = "0.1.0";
95
+ var version = "0.4.0";
96
96
  //#endregion
97
97
  //#region src/user-agent.ts
98
98
  const HTTP_TOKEN_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
@@ -305,7 +305,12 @@ const accountBalanceResponseSchema = v.object({
305
305
  balance: moneyJsonSchema,
306
306
  balances: v.optional(v.object({
307
307
  total: moneyJsonSchema,
308
- available: moneyJsonSchema
308
+ available: moneyJsonSchema,
309
+ byNetwork: v.optional(v.array(v.object({
310
+ network: v.string(),
311
+ total: moneyJsonSchema,
312
+ available: moneyJsonSchema
313
+ })))
309
314
  }))
310
315
  });
311
316
  const ACCOUNT_TRANSACTION_TYPES = [
@@ -351,6 +356,7 @@ const accountTransactionSchema = v.object({
351
356
  amount: moneyJsonSchema,
352
357
  fee: moneyJsonSchema,
353
358
  currency: v.string(),
359
+ network: v.optional(v.string()),
354
360
  counterpartyName: v.optional(v.string()),
355
361
  method: v.optional(extensibleStringSchema(ACCOUNT_TRANSACTION_METHODS)),
356
362
  memo: v.optional(v.string()),
@@ -371,6 +377,11 @@ const accountDepositAddressResponseSchema = v.object({
371
377
  liquidationAddressId: v.optional(v.string()),
372
378
  source: extensibleStringSchema(ACCOUNT_DEPOSIT_ADDRESS_SOURCES)
373
379
  });
380
+ const walletCounterpartyRailSchema = v.object({
381
+ type: v.literal("wallet"),
382
+ walletAddress: v.custom((input) => typeof input === "string" && /^0x[0-9a-fA-F]{40}$/.test(input)),
383
+ network: v.custom((input) => typeof input === "string" && input.length > 0)
384
+ });
374
385
  const counterpartySchema = v.object({
375
386
  id: v.string(),
376
387
  name: v.string(),
@@ -404,7 +415,19 @@ const intentNextActionSchema = v.object({
404
415
  bodyHash: v.string(),
405
416
  expiresAt: isoTimestampSchema,
406
417
  prepareToken: v.string()
407
- })
418
+ }),
419
+ movement: v.optional(v.object({
420
+ planId: v.string(),
421
+ signerAddress: v.string(),
422
+ digests: v.array(v.string()),
423
+ instructions: v.array(v.looseObject({
424
+ kind: v.string(),
425
+ network: v.string(),
426
+ signerAddress: v.string(),
427
+ digest: v.string(),
428
+ payload: v.record(v.string(), v.unknown())
429
+ }))
430
+ }))
408
431
  });
409
432
  const INTENT_ACTION_TYPES = [
410
433
  "send",
@@ -415,6 +438,50 @@ const INTENT_ACTION_TYPES = [
415
438
  "mpp",
416
439
  "policy_override"
417
440
  ];
441
+ const CROSS_CHAIN_STATES = [
442
+ "burning",
443
+ "attesting",
444
+ "minting",
445
+ "ready",
446
+ "delivering",
447
+ "delivered",
448
+ "failed",
449
+ "manual_review"
450
+ ];
451
+ const CROSS_CHAIN_NEXT_STEPS = [
452
+ "wait",
453
+ "continue",
454
+ "done",
455
+ "blocked"
456
+ ];
457
+ const crossChainSendSchema = v.object({
458
+ id: v.string(),
459
+ sourceNetwork: v.string(),
460
+ destinationNetwork: v.string(),
461
+ state: extensibleStringSchema(CROSS_CHAIN_STATES),
462
+ nextStep: extensibleStringSchema(CROSS_CHAIN_NEXT_STEPS),
463
+ readyAt: v.nullable(isoTimestampSchema),
464
+ deliveredAt: v.nullable(isoTimestampSchema),
465
+ failureReason: v.nullable(v.string())
466
+ });
467
+ const MOVEMENT_NEXT_STEPS = [
468
+ "wait",
469
+ "continue",
470
+ "done",
471
+ "blocked"
472
+ ];
473
+ const movementSchema = v.object({
474
+ planId: v.string(),
475
+ nextStep: extensibleStringSchema(MOVEMENT_NEXT_STEPS)
476
+ });
477
+ const movementContinuationSchema = v.object({ planId: v.string() });
478
+ const continueIntentResponseSchema = v.object({
479
+ id: v.string(),
480
+ status: intentStatusSchema,
481
+ crossChain: v.optional(crossChainSendSchema),
482
+ movement: v.optional(movementContinuationSchema),
483
+ nextAction: intentNextActionSchema
484
+ });
418
485
  const wireIntentResultSchema = v.object({
419
486
  id: v.string(),
420
487
  accountId: v.optional(v.string()),
@@ -425,6 +492,8 @@ const wireIntentResultSchema = v.object({
425
492
  replayed: v.optional(v.boolean(), false),
426
493
  data: v.optional(v.nullable(v.record(v.string(), v.unknown())), null),
427
494
  metadata: v.optional(v.object({ dataUrl: v.nullable(v.string()) })),
495
+ crossChain: v.optional(crossChainSendSchema),
496
+ movement: v.optional(movementSchema),
428
497
  nextAction: v.optional(intentNextActionSchema)
429
498
  });
430
499
  function toIntentResult(wire) {
@@ -438,16 +507,41 @@ function x402CredentialFromIntentData(data) {
438
507
  const legacy = v.safeParse(legacyX402IntentDataSchema, data);
439
508
  return legacy.success ? legacy.output.x402.paymentSignature : void 0;
440
509
  }
441
- const sendMethodSchema = v.picklist(SEND_METHODS);
442
- const sendActionSchema = v.object({
443
- type: v.literal("send"),
444
- accountId: v.optional(v.pipe(v.string(), v.minLength(1))),
445
- counterpartyRailId: v.string(),
446
- amount: v.string(),
447
- method: sendMethodSchema,
448
- memo: v.optional(v.string()),
449
- description: v.optional(v.string())
450
- });
510
+ v.picklist(SEND_METHODS);
511
+ const sendActionSchemasByMethod = {
512
+ ach: v.object({
513
+ type: v.literal("send"),
514
+ accountId: v.optional(v.pipe(v.string(), v.minLength(1))),
515
+ counterpartyRailId: v.string(),
516
+ amount: v.string(),
517
+ description: v.optional(v.string()),
518
+ method: v.literal("ach"),
519
+ memo: v.optional(v.string())
520
+ }),
521
+ wire: v.object({
522
+ type: v.literal("send"),
523
+ accountId: v.optional(v.pipe(v.string(), v.minLength(1))),
524
+ counterpartyRailId: v.string(),
525
+ amount: v.string(),
526
+ description: v.optional(v.string()),
527
+ method: v.literal("wire"),
528
+ memo: v.optional(v.string())
529
+ }),
530
+ "on-chain": v.object({
531
+ type: v.literal("send"),
532
+ accountId: v.optional(v.pipe(v.string(), v.minLength(1))),
533
+ counterpartyRailId: v.string(),
534
+ amount: v.string(),
535
+ description: v.optional(v.string()),
536
+ method: v.literal("on-chain"),
537
+ memo: v.optional(v.string())
538
+ })
539
+ };
540
+ const sendActionSchema = v.variant("method", [
541
+ sendActionSchemasByMethod.ach,
542
+ sendActionSchemasByMethod.wire,
543
+ sendActionSchemasByMethod["on-chain"]
544
+ ]);
451
545
  const transferActionSchema = v.object({
452
546
  type: v.literal("transfer"),
453
547
  accountId: v.string(),
@@ -589,12 +683,16 @@ var ApiError = class extends Error {
589
683
  status;
590
684
  code;
591
685
  details;
592
- constructor(status, message, code, details) {
593
- super(code ? `API error ${status} [${code}]: ${message}` : `API error ${status}: ${message}`);
686
+ fields;
687
+ constructor(status, message, code, details, fields) {
688
+ const explanations = Object.entries(fields ?? {}).map(([path, explanation]) => path === "request" ? explanation : `${path}: ${explanation}`).join("; ");
689
+ const description = explanations ? `${message}: ${explanations}` : message;
690
+ super(code ? `API error ${status} [${code}]: ${description}` : `API error ${status}: ${description}`);
594
691
  this.name = "ApiError";
595
692
  this.status = status;
596
693
  this.code = code;
597
694
  this.details = details;
695
+ this.fields = fields;
598
696
  }
599
697
  };
600
698
  var TimeoutError = class extends Error {
@@ -609,13 +707,14 @@ const errorBodySchema = v.object({
609
707
  error: v.nullish(v.string()),
610
708
  message: v.nullish(v.string()),
611
709
  code: v.nullish(v.string()),
612
- details: v.nullish(v.record(v.string(), v.unknown()))
710
+ details: v.nullish(v.record(v.string(), v.unknown())),
711
+ fields: v.fallback(v.nullish(v.pipe(v.unknown(), v.check((value) => !Array.isArray(value)), v.record(v.string(), v.string()))), void 0)
613
712
  });
614
713
  async function apiErrorFromResponse(res) {
615
714
  const parsed = v.safeParse(errorBodySchema, await res.json().catch(() => void 0));
616
715
  const body = parsed.success ? parsed.output : {};
617
- if (body.error != null && body.message != null) return new ApiError(res.status, body.message, body.code ?? body.error, body.details ?? void 0);
618
- return new ApiError(res.status, body.error ?? body.message ?? res.statusText, body.code ?? void 0, body.details ?? void 0);
716
+ if (body.error != null && body.message != null) return new ApiError(res.status, body.message, body.code ?? body.error, body.details ?? void 0, body.fields ?? void 0);
717
+ return new ApiError(res.status, body.error ?? body.message ?? res.statusText, body.code ?? void 0, body.details ?? void 0, body.fields ?? void 0);
619
718
  }
620
719
  async function parseApiResponse(res, schema) {
621
720
  if (!res.ok) throw await apiErrorFromResponse(res);
@@ -635,6 +734,27 @@ var IntentSubmitError = class extends Error {
635
734
  const DEFAULT_BASE_URL = "https://api.catena.com";
636
735
  const DEFAULT_TIMEOUT_MS = 8e4;
637
736
  const MAX_TIMEOUT_MS = 2147483647;
737
+ const digestBatchBodySchema = v.looseObject({ parameters: v.looseObject({ payloads: v.pipe(v.array(v.unknown()), v.minLength(1)) }) });
738
+ let movementsModule;
739
+ let movementsLoadFailure;
740
+ function loadMovements() {
741
+ movementsModule ??= import("./movements.mjs").then((loaded) => loaded, (reason) => {
742
+ movementsLoadFailure = reason;
743
+ });
744
+ return movementsModule;
745
+ }
746
+ async function continuationKinds() {
747
+ return await loadMovements() === void 0 ? "crossChain" : "crossChain, movement";
748
+ }
749
+ const digestBatchActivitySchema = v.looseObject({ type: v.literal("ACTIVITY_TYPE_SIGN_RAW_PAYLOADS") });
750
+ function signsADigestBatch(body) {
751
+ try {
752
+ const parsed = JSON.parse(body);
753
+ return v.safeParse(digestBatchBodySchema, parsed).success || v.safeParse(digestBatchActivitySchema, parsed).success;
754
+ } catch {
755
+ return false;
756
+ }
757
+ }
638
758
  var CatenaClient = class {
639
759
  #baseUrl;
640
760
  #userAgent;
@@ -769,7 +889,7 @@ var CatenaClient = class {
769
889
  if (!nextAction) return toIntentResult(created);
770
890
  let submitRequest;
771
891
  try {
772
- const stamp = await this.#stampNextAction(nextAction);
892
+ const stamp = await this.#stampNextAction(nextAction, params.action);
773
893
  submitRequest = await this.#signedRequest({
774
894
  method: "POST",
775
895
  path: `/intents/${encodeURIComponent(created.id)}/submit-stamp`,
@@ -803,8 +923,9 @@ var CatenaClient = class {
803
923
  headers: { "Idempotency-Key": params.idempotencyKey }
804
924
  }, wireIntentResultSchema);
805
925
  }
806
- async #stampNextAction(nextAction) {
926
+ async #stampNextAction(nextAction, action) {
807
927
  if (nextAction.signer.publicKeyHex.toLowerCase() !== this.#keypair.publicKeyHex.toLowerCase()) throw new Error(`Local signer public key does not match requested signer ${nextAction.signer.publicKeyHex}; linked credential has ${this.#keypair.publicKeyHex}`);
928
+ await this.#reviewMovement(nextAction, action);
808
929
  const stamp = await new ApiKeyStamper({
809
930
  apiPublicKey: this.#keypair.publicKeyHex,
810
931
  apiPrivateKey: this.#keypair.privateKeyHex
@@ -815,6 +936,23 @@ var CatenaClient = class {
815
936
  stampHeaderValue: stamp.stampHeaderValue
816
937
  };
817
938
  }
939
+ async #reviewMovement(nextAction, action) {
940
+ const movement = nextAction.movement;
941
+ if (movement === void 0) {
942
+ if (signsADigestBatch(nextAction.preparedBody.body)) throw new Error("Refusing to approve this payment: it would sign pre-hashed digests with nothing describing what they commit to. Nothing was signed.");
943
+ return;
944
+ }
945
+ const movements = await loadMovements();
946
+ if (movements === void 0) throw new Error("Refusing to approve this payment: its digests cannot be checked without the optional `viem` peer dependency (>=2.24.0). Nothing was signed.", { cause: movementsLoadFailure });
947
+ const { verifyPreparedMovement } = movements;
948
+ const verdict = verifyPreparedMovement({
949
+ signerAddress: movement.signerAddress,
950
+ digests: movement.digests,
951
+ instructions: movement.instructions,
952
+ body: nextAction.preparedBody.body
953
+ }, action !== void 0 && "amount" in action ? { amount: action.amount } : void 0);
954
+ if (!verdict.ok) throw new Error(`Refusing to approve this payment: ${verdict.reason} Nothing was signed.`);
955
+ }
818
956
  async #submitStamp(request, replayed) {
819
957
  const intent = toIntentResult((await this.#dispatch(request, submitStampResponseSchema)).intent);
820
958
  return replayed ? {
@@ -828,6 +966,43 @@ var CatenaClient = class {
828
966
  path: `/intents/${encodeURIComponent(id)}`
829
967
  }, wireIntentResultSchema));
830
968
  }
969
+ async continueIntent(params) {
970
+ const prepared = await this.#request({
971
+ method: "POST",
972
+ path: `/intents/${encodeURIComponent(params.intentId)}/continue`,
973
+ headers: {
974
+ "Idempotency-Key": params.idempotencyKey ?? crypto.randomUUID(),
975
+ "Catena-Continuation-Kinds": await continuationKinds()
976
+ }
977
+ }, continueIntentResponseSchema);
978
+ let submitRequest;
979
+ try {
980
+ const stamp = await this.#stampNextAction(prepared.nextAction, params.action);
981
+ submitRequest = await this.#signedRequest({
982
+ method: "POST",
983
+ path: `/intents/${encodeURIComponent(params.intentId)}/submit-stamp`,
984
+ body: {
985
+ preparedBody: prepared.nextAction.preparedBody,
986
+ stamp
987
+ }
988
+ });
989
+ } catch (cause) {
990
+ throw new IntentSubmitError({
991
+ intentId: params.intentId,
992
+ outcome: "not-submitted",
993
+ cause
994
+ });
995
+ }
996
+ try {
997
+ return await this.#submitStamp(submitRequest, false);
998
+ } catch (cause) {
999
+ throw new IntentSubmitError({
1000
+ intentId: params.intentId,
1001
+ outcome: "unknown",
1002
+ cause
1003
+ });
1004
+ }
1005
+ }
831
1006
  async unlinkAgent() {
832
1007
  await this.#request({
833
1008
  method: "POST",
@@ -854,4 +1029,4 @@ function createCatenaClient(options = {}) {
854
1029
  });
855
1030
  }
856
1031
  //#endregion
857
- export { extractRelayableOffer as C, x402ResourceSchema as S, paymentCredentialFromIntentData as T, POLICY_RULE_ACTIONS as _, ACCOUNT_AGGREGATION_SCOPES as a, x402CredentialFromIntentData as b, ACCOUNT_TRANSACTION_MOVEMENT_STATUSES as c, ACTOR_AGGREGATION_SCOPES as d, COUNTERPARTY_ACTIONS as f, POLICY_OVERRIDE_CAPABILITIES as g, POLICY_CAPABILITIES as h, createCatenaClient as i, ACCOUNT_TRANSACTION_STATUSES as l, INTENT_ACTION_TYPES as m, IntentSubmitError as n, ACCOUNT_DEPOSIT_ADDRESS_SOURCES as o, COUNTERPARTY_RULE_MODES as p, TimeoutError as r, ACCOUNT_TRANSACTION_METHODS as s, ApiError as t, ACCOUNT_TRANSACTION_TYPES as u, POLICY_RULE_TYPES as v, extractRelayableReceipt as w, x402PaymentRequirementsSchema as x, SEND_METHODS as y };
1032
+ export { walletCounterpartyRailSchema as C, extractRelayableOffer as D, x402ResourceSchema as E, extractRelayableReceipt as O, SEND_METHODS as S, x402PaymentRequirementsSchema as T, MOVEMENT_NEXT_STEPS as _, ACCOUNT_AGGREGATION_SCOPES as a, POLICY_RULE_ACTIONS as b, ACCOUNT_TRANSACTION_MOVEMENT_STATUSES as c, ACTOR_AGGREGATION_SCOPES as d, COUNTERPARTY_ACTIONS as f, INTENT_ACTION_TYPES as g, CROSS_CHAIN_STATES as h, createCatenaClient as i, paymentCredentialFromIntentData as k, ACCOUNT_TRANSACTION_STATUSES as l, CROSS_CHAIN_NEXT_STEPS as m, IntentSubmitError as n, ACCOUNT_DEPOSIT_ADDRESS_SOURCES as o, COUNTERPARTY_RULE_MODES as p, TimeoutError as r, ACCOUNT_TRANSACTION_METHODS as s, ApiError as t, ACCOUNT_TRANSACTION_TYPES as u, POLICY_CAPABILITIES as v, x402CredentialFromIntentData as w, POLICY_RULE_TYPES as x, POLICY_OVERRIDE_CAPABILITIES as y };