primcli 1.6.1 → 1.8.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 +7 -0
- package/dist/oclif/index.js +715 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,6 +92,13 @@ primitive payments charge --network base-sepolia --amount-usdc 0.01 --json > cha
|
|
|
92
92
|
primitive payments pay --challenge-file challenge.json
|
|
93
93
|
cat challenge.json | primitive payments pay
|
|
94
94
|
|
|
95
|
+
# Email-native flow: the payee issues a challenge over an email thread.
|
|
96
|
+
primitive payments create-email-challenge --from payee@your-domain.example \
|
|
97
|
+
--to payer@their-domain.example --amount 10000 --network base-sepolia
|
|
98
|
+
# Payer: sign a received email challenge into an interaction.json payment step.
|
|
99
|
+
# This does NOT send; attach the output to your reply (e.g. with `primitive reply`).
|
|
100
|
+
primitive payments pay-email-step --challenge-file challenge.json > interaction.json
|
|
101
|
+
|
|
95
102
|
# Inspect a challenge by id, or list your registered payout addresses.
|
|
96
103
|
primitive payments get-challenge --id <challenge-id>
|
|
97
104
|
primitive payments list-payout-addresses
|
package/dist/oclif/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import process$1 from "node:process";
|
|
|
7
7
|
import { createInterface } from "node:readline/promises";
|
|
8
8
|
import { hostname } from "node:os";
|
|
9
9
|
import { spawn } from "node:child_process";
|
|
10
|
-
import { concat, hexToBytes, keccak256, stringToBytes } from "viem";
|
|
10
|
+
import { concat, getAddress, hexToBytes, keccak256, stringToBytes } from "viem";
|
|
11
11
|
import { privateKeyToAccount } from "viem/accounts";
|
|
12
12
|
//#region \0rolldown/runtime.js
|
|
13
13
|
var __defProp = Object.defineProperty;
|
|
@@ -31,6 +31,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
|
31
31
|
createAgentAccount: () => createAgentAccount,
|
|
32
32
|
createAgentClaimLink: () => createAgentClaimLink,
|
|
33
33
|
createChallenge: () => createChallenge,
|
|
34
|
+
createEmailChallenge: () => createEmailChallenge,
|
|
34
35
|
createEndpoint: () => createEndpoint,
|
|
35
36
|
createFilter: () => createFilter,
|
|
36
37
|
createFunction: () => createFunction,
|
|
@@ -1671,6 +1672,34 @@ const registerPayoutAddress = (options) => (options.client ?? client).post({
|
|
|
1671
1672
|
}
|
|
1672
1673
|
});
|
|
1673
1674
|
/**
|
|
1675
|
+
* Create an email-native payment challenge
|
|
1676
|
+
*
|
|
1677
|
+
* Issue an x402 payment challenge over a real email thread (the payee
|
|
1678
|
+
* side). Unlike `createChallenge` (which mints a synthetic challenge id),
|
|
1679
|
+
* this sends the challenge as an email from `from` to `to` and binds the
|
|
1680
|
+
* payment to that DKIM-authenticated thread. The `pay_to` address and the
|
|
1681
|
+
* token asset are resolved server-side from your registered default payout
|
|
1682
|
+
* address for the network, never from the request. The response carries
|
|
1683
|
+
* the thread's `interaction_id` plus the `challenge` (the
|
|
1684
|
+
* `payment_requirements`, the `nonce_binding`, and `expires_at`) the payer
|
|
1685
|
+
* needs to sign; the payer replies with a signed `payment` interaction
|
|
1686
|
+
* step. Amounts are in token base units (USDC has 6 decimals, so `"10000"`
|
|
1687
|
+
* is 0.01 USDC).
|
|
1688
|
+
*
|
|
1689
|
+
*/
|
|
1690
|
+
const createEmailChallenge = (options) => (options.client ?? client).post({
|
|
1691
|
+
security: [{
|
|
1692
|
+
scheme: "bearer",
|
|
1693
|
+
type: "http"
|
|
1694
|
+
}],
|
|
1695
|
+
url: "/x402/email-challenges",
|
|
1696
|
+
...options,
|
|
1697
|
+
headers: {
|
|
1698
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
1699
|
+
...options.headers
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
/**
|
|
1674
1703
|
* Create a payment challenge
|
|
1675
1704
|
*
|
|
1676
1705
|
* Create an x402 payment challenge (the payee side of a payment). The
|
|
@@ -4172,6 +4201,40 @@ const openapiDocument = {
|
|
|
4172
4201
|
}
|
|
4173
4202
|
}
|
|
4174
4203
|
},
|
|
4204
|
+
"/x402/email-challenges": { "post": {
|
|
4205
|
+
"operationId": "createEmailChallenge",
|
|
4206
|
+
"summary": "Create an email-native payment challenge",
|
|
4207
|
+
"description": "Issue an x402 payment challenge over a real email thread (the payee\nside). Unlike `createChallenge` (which mints a synthetic challenge id),\nthis sends the challenge as an email from `from` to `to` and binds the\npayment to that DKIM-authenticated thread. The `pay_to` address and the\ntoken asset are resolved server-side from your registered default payout\naddress for the network, never from the request. The response carries\nthe thread's `interaction_id` plus the `challenge` (the\n`payment_requirements`, the `nonce_binding`, and `expires_at`) the payer\nneeds to sign; the payer replies with a signed `payment` interaction\nstep. Amounts are in token base units (USDC has 6 decimals, so `\"10000\"`\nis 0.01 USDC).\n",
|
|
4208
|
+
"tags": ["Payments"],
|
|
4209
|
+
"security": [{ "BearerAuth": [] }],
|
|
4210
|
+
"parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
|
|
4211
|
+
"requestBody": {
|
|
4212
|
+
"required": true,
|
|
4213
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateEmailChallengeInput" } } }
|
|
4214
|
+
},
|
|
4215
|
+
"responses": {
|
|
4216
|
+
"200": {
|
|
4217
|
+
"description": "Idempotent replay: a request with a previously-used idempotency key\nreturns the original issued challenge without sending a second email.\n",
|
|
4218
|
+
"content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
|
|
4219
|
+
"type": "object",
|
|
4220
|
+
"properties": { "data": { "$ref": "#/components/schemas/X402EmailChallenge" } }
|
|
4221
|
+
}] } } }
|
|
4222
|
+
},
|
|
4223
|
+
"201": {
|
|
4224
|
+
"description": "Email challenge issued",
|
|
4225
|
+
"content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
|
|
4226
|
+
"type": "object",
|
|
4227
|
+
"properties": { "data": { "$ref": "#/components/schemas/X402EmailChallenge" } }
|
|
4228
|
+
}] } } }
|
|
4229
|
+
},
|
|
4230
|
+
"400": { "$ref": "#/components/responses/ValidationError" },
|
|
4231
|
+
"401": { "$ref": "#/components/responses/Unauthorized" },
|
|
4232
|
+
"403": { "$ref": "#/components/responses/Forbidden" },
|
|
4233
|
+
"409": { "$ref": "#/components/responses/Conflict" },
|
|
4234
|
+
"422": { "$ref": "#/components/responses/UnprocessableEntity" },
|
|
4235
|
+
"429": { "$ref": "#/components/responses/RateLimited" }
|
|
4236
|
+
}
|
|
4237
|
+
} },
|
|
4175
4238
|
"/x402/challenges": { "post": {
|
|
4176
4239
|
"operationId": "createChallenge",
|
|
4177
4240
|
"summary": "Create a payment challenge",
|
|
@@ -4341,6 +4404,16 @@ const openapiDocument = {
|
|
|
4341
4404
|
},
|
|
4342
4405
|
"description": "Resource UUID"
|
|
4343
4406
|
},
|
|
4407
|
+
"IdempotencyKey": {
|
|
4408
|
+
"name": "idempotency-key",
|
|
4409
|
+
"in": "header",
|
|
4410
|
+
"required": false,
|
|
4411
|
+
"schema": {
|
|
4412
|
+
"type": "string",
|
|
4413
|
+
"maxLength": 255
|
|
4414
|
+
},
|
|
4415
|
+
"description": "Optional idempotency key. Retrying a request with the same key returns\nthe original result instead of repeating the side effect (for\n`createEmailChallenge`, re-sending the email).\n"
|
|
4416
|
+
},
|
|
4344
4417
|
"Cursor": {
|
|
4345
4418
|
"name": "cursor",
|
|
4346
4419
|
"in": "query",
|
|
@@ -4781,6 +4854,94 @@ const openapiDocument = {
|
|
|
4781
4854
|
"expires_at"
|
|
4782
4855
|
]
|
|
4783
4856
|
},
|
|
4857
|
+
"CreateEmailChallengeInput": {
|
|
4858
|
+
"type": "object",
|
|
4859
|
+
"additionalProperties": false,
|
|
4860
|
+
"description": "Issue a payment challenge over an email thread. `from` is your sending\naddress (the funds receiver; ownership is enforced at send, exactly as\nfor outbound mail) and `to` is the payer's address. The `pay_to` payout\nwallet and the token asset are resolved server-side, never taken from\nthe request.\n",
|
|
4861
|
+
"properties": {
|
|
4862
|
+
"from": {
|
|
4863
|
+
"type": "string",
|
|
4864
|
+
"format": "email",
|
|
4865
|
+
"description": "Your sending address (the payee / funds receiver). Must be an\naddress your org is allowed to send from.\n"
|
|
4866
|
+
},
|
|
4867
|
+
"to": {
|
|
4868
|
+
"type": "string",
|
|
4869
|
+
"format": "email",
|
|
4870
|
+
"description": "The payer's email address the challenge is sent to."
|
|
4871
|
+
},
|
|
4872
|
+
"amount": {
|
|
4873
|
+
"type": "string",
|
|
4874
|
+
"pattern": "^[1-9][0-9]{0,38}$",
|
|
4875
|
+
"description": "Amount to collect, in token base units. USDC has 6 decimals, so\n`\"10000\"` is 0.01 USDC.\n"
|
|
4876
|
+
},
|
|
4877
|
+
"network": {
|
|
4878
|
+
"type": "string",
|
|
4879
|
+
"enum": ["base", "base-sepolia"]
|
|
4880
|
+
},
|
|
4881
|
+
"expires_in": {
|
|
4882
|
+
"type": "integer",
|
|
4883
|
+
"minimum": 60,
|
|
4884
|
+
"maximum": 86400,
|
|
4885
|
+
"description": "Seconds until the challenge expires. Defaults to 3600."
|
|
4886
|
+
},
|
|
4887
|
+
"resource": {
|
|
4888
|
+
"type": "string",
|
|
4889
|
+
"format": "uri",
|
|
4890
|
+
"maxLength": 2048,
|
|
4891
|
+
"description": "Optional URL identifying what is being paid for."
|
|
4892
|
+
},
|
|
4893
|
+
"description": {
|
|
4894
|
+
"type": "string",
|
|
4895
|
+
"maxLength": 512,
|
|
4896
|
+
"description": "Optional human-readable description of the payment."
|
|
4897
|
+
}
|
|
4898
|
+
},
|
|
4899
|
+
"required": [
|
|
4900
|
+
"from",
|
|
4901
|
+
"to",
|
|
4902
|
+
"amount",
|
|
4903
|
+
"network"
|
|
4904
|
+
]
|
|
4905
|
+
},
|
|
4906
|
+
"X402EmailChallengeDetails": {
|
|
4907
|
+
"type": "object",
|
|
4908
|
+
"description": "The challenge the payer needs to sign and pay, carried inside an\nemail-native challenge response.\n",
|
|
4909
|
+
"properties": {
|
|
4910
|
+
"payment_requirements": { "$ref": "#/components/schemas/X402PaymentRequirements" },
|
|
4911
|
+
"nonce_binding": { "$ref": "#/components/schemas/X402NonceBinding" },
|
|
4912
|
+
"expires_at": {
|
|
4913
|
+
"type": "string",
|
|
4914
|
+
"format": "date-time",
|
|
4915
|
+
"description": "ISO-8601 expiry of the challenge."
|
|
4916
|
+
}
|
|
4917
|
+
},
|
|
4918
|
+
"required": [
|
|
4919
|
+
"payment_requirements",
|
|
4920
|
+
"nonce_binding",
|
|
4921
|
+
"expires_at"
|
|
4922
|
+
]
|
|
4923
|
+
},
|
|
4924
|
+
"X402EmailChallenge": {
|
|
4925
|
+
"type": "object",
|
|
4926
|
+
"description": "The result of issuing an email-native payment challenge. `interaction_id`\nis the real email thread id (`uuid@domain`) the payment is bound to;\n`challenge_id` is the underlying challenge record. Hand the `challenge`\nto the payer, who replies with a signed `payment` interaction step (the\nSDK `payEmailChallenge` helper builds it).\n",
|
|
4927
|
+
"properties": {
|
|
4928
|
+
"interaction_id": {
|
|
4929
|
+
"type": "string",
|
|
4930
|
+
"description": "The email thread id (`uuid@domain`) the payment is bound to."
|
|
4931
|
+
},
|
|
4932
|
+
"challenge_id": {
|
|
4933
|
+
"type": "string",
|
|
4934
|
+
"format": "uuid",
|
|
4935
|
+
"description": "The underlying challenge record id."
|
|
4936
|
+
},
|
|
4937
|
+
"challenge": { "$ref": "#/components/schemas/X402EmailChallengeDetails" }
|
|
4938
|
+
},
|
|
4939
|
+
"required": [
|
|
4940
|
+
"interaction_id",
|
|
4941
|
+
"challenge_id",
|
|
4942
|
+
"challenge"
|
|
4943
|
+
]
|
|
4944
|
+
},
|
|
4784
4945
|
"X402PaymentPayload": {
|
|
4785
4946
|
"type": "object",
|
|
4786
4947
|
"description": "A signed x402 v1 `PaymentPayload`. The SDK `pay()` helper builds this;\ncallers rarely construct it by hand. Field names are x402-native.\n",
|
|
@@ -14629,6 +14790,178 @@ const operationManifest = [
|
|
|
14629
14790
|
"tag": "Payments",
|
|
14630
14791
|
"tagCommand": "payments"
|
|
14631
14792
|
},
|
|
14793
|
+
{
|
|
14794
|
+
"binaryResponse": false,
|
|
14795
|
+
"bodyRequired": true,
|
|
14796
|
+
"command": "create-email-challenge",
|
|
14797
|
+
"description": "Issue an x402 payment challenge over a real email thread (the payee\nside). Unlike `createChallenge` (which mints a synthetic challenge id),\nthis sends the challenge as an email from `from` to `to` and binds the\npayment to that DKIM-authenticated thread. The `pay_to` address and the\ntoken asset are resolved server-side from your registered default payout\naddress for the network, never from the request. The response carries\nthe thread's `interaction_id` plus the `challenge` (the\n`payment_requirements`, the `nonce_binding`, and `expires_at`) the payer\nneeds to sign; the payer replies with a signed `payment` interaction\nstep. Amounts are in token base units (USDC has 6 decimals, so `\"10000\"`\nis 0.01 USDC).\n",
|
|
14798
|
+
"hasJsonBody": true,
|
|
14799
|
+
"method": "POST",
|
|
14800
|
+
"operationId": "createEmailChallenge",
|
|
14801
|
+
"path": "/x402/email-challenges",
|
|
14802
|
+
"pathParams": [],
|
|
14803
|
+
"queryParams": [],
|
|
14804
|
+
"requestSchema": {
|
|
14805
|
+
"type": "object",
|
|
14806
|
+
"additionalProperties": false,
|
|
14807
|
+
"description": "Issue a payment challenge over an email thread. `from` is your sending\naddress (the funds receiver; ownership is enforced at send, exactly as\nfor outbound mail) and `to` is the payer's address. The `pay_to` payout\nwallet and the token asset are resolved server-side, never taken from\nthe request.\n",
|
|
14808
|
+
"properties": {
|
|
14809
|
+
"from": {
|
|
14810
|
+
"type": "string",
|
|
14811
|
+
"format": "email",
|
|
14812
|
+
"description": "Your sending address (the payee / funds receiver). Must be an\naddress your org is allowed to send from.\n"
|
|
14813
|
+
},
|
|
14814
|
+
"to": {
|
|
14815
|
+
"type": "string",
|
|
14816
|
+
"format": "email",
|
|
14817
|
+
"description": "The payer's email address the challenge is sent to."
|
|
14818
|
+
},
|
|
14819
|
+
"amount": {
|
|
14820
|
+
"type": "string",
|
|
14821
|
+
"pattern": "^[1-9][0-9]{0,38}$",
|
|
14822
|
+
"description": "Amount to collect, in token base units. USDC has 6 decimals, so\n`\"10000\"` is 0.01 USDC.\n"
|
|
14823
|
+
},
|
|
14824
|
+
"network": {
|
|
14825
|
+
"type": "string",
|
|
14826
|
+
"enum": ["base", "base-sepolia"]
|
|
14827
|
+
},
|
|
14828
|
+
"expires_in": {
|
|
14829
|
+
"type": "integer",
|
|
14830
|
+
"minimum": 60,
|
|
14831
|
+
"maximum": 86400,
|
|
14832
|
+
"description": "Seconds until the challenge expires. Defaults to 3600."
|
|
14833
|
+
},
|
|
14834
|
+
"resource": {
|
|
14835
|
+
"type": "string",
|
|
14836
|
+
"format": "uri",
|
|
14837
|
+
"maxLength": 2048,
|
|
14838
|
+
"description": "Optional URL identifying what is being paid for."
|
|
14839
|
+
},
|
|
14840
|
+
"description": {
|
|
14841
|
+
"type": "string",
|
|
14842
|
+
"maxLength": 512,
|
|
14843
|
+
"description": "Optional human-readable description of the payment."
|
|
14844
|
+
}
|
|
14845
|
+
},
|
|
14846
|
+
"required": [
|
|
14847
|
+
"from",
|
|
14848
|
+
"to",
|
|
14849
|
+
"amount",
|
|
14850
|
+
"network"
|
|
14851
|
+
]
|
|
14852
|
+
},
|
|
14853
|
+
"responseSchema": {
|
|
14854
|
+
"type": "object",
|
|
14855
|
+
"description": "The result of issuing an email-native payment challenge. `interaction_id`\nis the real email thread id (`uuid@domain`) the payment is bound to;\n`challenge_id` is the underlying challenge record. Hand the `challenge`\nto the payer, who replies with a signed `payment` interaction step (the\nSDK `payEmailChallenge` helper builds it).\n",
|
|
14856
|
+
"properties": {
|
|
14857
|
+
"interaction_id": {
|
|
14858
|
+
"type": "string",
|
|
14859
|
+
"description": "The email thread id (`uuid@domain`) the payment is bound to."
|
|
14860
|
+
},
|
|
14861
|
+
"challenge_id": {
|
|
14862
|
+
"type": "string",
|
|
14863
|
+
"format": "uuid",
|
|
14864
|
+
"description": "The underlying challenge record id."
|
|
14865
|
+
},
|
|
14866
|
+
"challenge": {
|
|
14867
|
+
"type": "object",
|
|
14868
|
+
"description": "The challenge the payer needs to sign and pay, carried inside an\nemail-native challenge response.\n",
|
|
14869
|
+
"properties": {
|
|
14870
|
+
"payment_requirements": {
|
|
14871
|
+
"type": "object",
|
|
14872
|
+
"description": "The x402 `PaymentRequirements` the payer signs over. Field names are\nx402's native camelCase, preserved byte-for-byte.\n",
|
|
14873
|
+
"properties": {
|
|
14874
|
+
"scheme": {
|
|
14875
|
+
"type": "string",
|
|
14876
|
+
"description": "The x402 settlement scheme. Always `exact` for v1.",
|
|
14877
|
+
"example": "exact"
|
|
14878
|
+
},
|
|
14879
|
+
"network": {
|
|
14880
|
+
"type": "string",
|
|
14881
|
+
"enum": ["base", "base-sepolia"]
|
|
14882
|
+
},
|
|
14883
|
+
"maxAmountRequired": {
|
|
14884
|
+
"type": "string",
|
|
14885
|
+
"description": "Amount in token base units."
|
|
14886
|
+
},
|
|
14887
|
+
"payTo": {
|
|
14888
|
+
"type": "string",
|
|
14889
|
+
"description": "The payee's resolved payout address (checksummed)."
|
|
14890
|
+
},
|
|
14891
|
+
"asset": {
|
|
14892
|
+
"type": "string",
|
|
14893
|
+
"description": "The token contract address (checksummed). USDC."
|
|
14894
|
+
},
|
|
14895
|
+
"resource": { "type": "string" },
|
|
14896
|
+
"description": { "type": "string" },
|
|
14897
|
+
"maxTimeoutSeconds": { "type": "integer" },
|
|
14898
|
+
"extra": {
|
|
14899
|
+
"type": "object",
|
|
14900
|
+
"description": "The token's load-bearing EIP-712 domain params. `name` differs by\nchain (Base mainnet USDC is `USD Coin`, Base Sepolia is `USDC`); a\nwrong value produces a signature the verifier rejects.\n",
|
|
14901
|
+
"properties": {
|
|
14902
|
+
"name": { "type": "string" },
|
|
14903
|
+
"version": { "type": "string" }
|
|
14904
|
+
},
|
|
14905
|
+
"required": ["name", "version"]
|
|
14906
|
+
}
|
|
14907
|
+
},
|
|
14908
|
+
"required": [
|
|
14909
|
+
"scheme",
|
|
14910
|
+
"network",
|
|
14911
|
+
"maxAmountRequired",
|
|
14912
|
+
"payTo",
|
|
14913
|
+
"asset",
|
|
14914
|
+
"extra"
|
|
14915
|
+
]
|
|
14916
|
+
},
|
|
14917
|
+
"nonce_binding": {
|
|
14918
|
+
"type": "object",
|
|
14919
|
+
"description": "The interaction binding the payer hashes into the EIP-3009 nonce\n(`deriveEip3009Nonce`). Pinning the nonce to this binding is what lets an\nx402 payment ride asynchronous transports safely: a replayed challenge\ncan't redirect funds and a signed payment can't settle twice.\n",
|
|
14920
|
+
"properties": {
|
|
14921
|
+
"interaction_id": {
|
|
14922
|
+
"type": "string",
|
|
14923
|
+
"description": "Interaction id, including its `@domain` part."
|
|
14924
|
+
},
|
|
14925
|
+
"challenge_step_id": {
|
|
14926
|
+
"type": "string",
|
|
14927
|
+
"format": "uuid"
|
|
14928
|
+
},
|
|
14929
|
+
"challenge_nonce": {
|
|
14930
|
+
"type": "string",
|
|
14931
|
+
"pattern": "^[0-9a-f]{64}$",
|
|
14932
|
+
"description": "32 random bytes as 64 lowercase hex chars."
|
|
14933
|
+
}
|
|
14934
|
+
},
|
|
14935
|
+
"required": [
|
|
14936
|
+
"interaction_id",
|
|
14937
|
+
"challenge_step_id",
|
|
14938
|
+
"challenge_nonce"
|
|
14939
|
+
]
|
|
14940
|
+
},
|
|
14941
|
+
"expires_at": {
|
|
14942
|
+
"type": "string",
|
|
14943
|
+
"format": "date-time",
|
|
14944
|
+
"description": "ISO-8601 expiry of the challenge."
|
|
14945
|
+
}
|
|
14946
|
+
},
|
|
14947
|
+
"required": [
|
|
14948
|
+
"payment_requirements",
|
|
14949
|
+
"nonce_binding",
|
|
14950
|
+
"expires_at"
|
|
14951
|
+
]
|
|
14952
|
+
}
|
|
14953
|
+
},
|
|
14954
|
+
"required": [
|
|
14955
|
+
"interaction_id",
|
|
14956
|
+
"challenge_id",
|
|
14957
|
+
"challenge"
|
|
14958
|
+
]
|
|
14959
|
+
},
|
|
14960
|
+
"sdkName": "createEmailChallenge",
|
|
14961
|
+
"summary": "Create an email-native payment challenge",
|
|
14962
|
+
"tag": "Payments",
|
|
14963
|
+
"tagCommand": "payments"
|
|
14964
|
+
},
|
|
14632
14965
|
{
|
|
14633
14966
|
"binaryResponse": false,
|
|
14634
14967
|
"bodyRequired": false,
|
|
@@ -21020,8 +21353,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
21020
21353
|
name: "Primitive Team",
|
|
21021
21354
|
url: "https://primitive.dev"
|
|
21022
21355
|
};
|
|
21023
|
-
const SDK_VERSION_RANGE = "^1.
|
|
21024
|
-
const CLI_VERSION_RANGE = "^1.
|
|
21356
|
+
const SDK_VERSION_RANGE = "^1.8.0";
|
|
21357
|
+
const CLI_VERSION_RANGE = "^1.8.0";
|
|
21025
21358
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
21026
21359
|
function renderHandler() {
|
|
21027
21360
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -24509,6 +24842,43 @@ function buildPayoutRegistrationMessage(input) {
|
|
|
24509
24842
|
`issued: ${input.issuedAt}`
|
|
24510
24843
|
].join("\n");
|
|
24511
24844
|
}
|
|
24845
|
+
/**
|
|
24846
|
+
* The protocol the email-native payment interaction runs (`x402.payment/1`).
|
|
24847
|
+
* The payer's reply carries the `payment` step of this protocol.
|
|
24848
|
+
*/
|
|
24849
|
+
const X402_INTERACTION_PROTOCOL = "x402.payment";
|
|
24850
|
+
/** A UUID (used for `interaction_id`'s local part and the step ids). */
|
|
24851
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
24852
|
+
/** An interaction id is `uuid@domain`. */
|
|
24853
|
+
const WIRE_ID_RE = new RegExp(`^${UUID_RE.source.slice(1, -1)}@[^\\s@]+$`, "i");
|
|
24854
|
+
/**
|
|
24855
|
+
* Build the section-2.3 interaction.json envelope for a `payment` step. Pure: no
|
|
24856
|
+
* I/O. `payment` is the signed exact-EVM payload (from
|
|
24857
|
+
* `buildExactEvmPaymentPayload`); `prevStepId` is the challenge step id this
|
|
24858
|
+
* payment answers, and `stepId` is a fresh UUID for the payment step. Returns
|
|
24859
|
+
* the envelope and its canonical JSON, so the bytes the platform reads back are
|
|
24860
|
+
* exactly the ones produced here.
|
|
24861
|
+
*/
|
|
24862
|
+
function buildPaymentStepEnvelope(params) {
|
|
24863
|
+
if (!WIRE_ID_RE.test(params.interactionId)) throw new Error("buildPaymentStepEnvelope: interactionId must be uuid@domain");
|
|
24864
|
+
if (!UUID_RE.test(params.stepId)) throw new Error("buildPaymentStepEnvelope: stepId must be a uuid");
|
|
24865
|
+
if (!UUID_RE.test(params.prevStepId)) throw new Error("buildPaymentStepEnvelope: prevStepId must be a uuid");
|
|
24866
|
+
const envelope = {
|
|
24867
|
+
interaction_version: 1,
|
|
24868
|
+
interaction_id: params.interactionId,
|
|
24869
|
+
protocol: X402_INTERACTION_PROTOCOL,
|
|
24870
|
+
protocol_version: 1,
|
|
24871
|
+
step: "payment",
|
|
24872
|
+
step_id: params.stepId,
|
|
24873
|
+
prev_step_id: params.prevStepId,
|
|
24874
|
+
expires_at: params.expiresAt ?? null,
|
|
24875
|
+
payload: { payment: params.payment }
|
|
24876
|
+
};
|
|
24877
|
+
return {
|
|
24878
|
+
envelope,
|
|
24879
|
+
json: JSON.stringify(envelope)
|
|
24880
|
+
};
|
|
24881
|
+
}
|
|
24512
24882
|
/** Assemble the wire payload from a signed authorization. */
|
|
24513
24883
|
function toPaymentPayload(network, auth, signature) {
|
|
24514
24884
|
return {
|
|
@@ -24528,12 +24898,77 @@ function toPaymentPayload(network, auth, signature) {
|
|
|
24528
24898
|
}
|
|
24529
24899
|
};
|
|
24530
24900
|
}
|
|
24901
|
+
/**
|
|
24902
|
+
* Compute the EIP-3009 validity window for a payment. `validBefore` is the
|
|
24903
|
+
* value that governs on-chain validity, so it MUST cover the challenge's
|
|
24904
|
+
* `expires_at` plus a settlement margin; `validAfter` is set generously in the
|
|
24905
|
+
* past for clock skew. The total window is hard-capped at `maxWindowSec` so
|
|
24906
|
+
* neither a far-future `challengeExpiresAtSec` nor a widened margin can produce
|
|
24907
|
+
* a window the platform verifier would later reject.
|
|
24908
|
+
*/
|
|
24909
|
+
function computePaymentValidityWindow(params) {
|
|
24910
|
+
const margin = params.settlementMarginSec ?? 300;
|
|
24911
|
+
const skew = params.clockSkewSec ?? 300;
|
|
24912
|
+
const validBefore = BigInt(params.challengeExpiresAtSec + margin);
|
|
24913
|
+
const validAfter = BigInt(params.nowSec - skew);
|
|
24914
|
+
if (validBefore <= validAfter) throw new Error("invalid validity window: validBefore must be after validAfter (challenge already expired?)");
|
|
24915
|
+
const maxWindow = BigInt(params.maxWindowSec ?? 86400);
|
|
24916
|
+
if (validBefore - validAfter > maxWindow) throw new Error(`invalid validity window: total window exceeds the ${maxWindow}s cap (challenge expiry too far out?)`);
|
|
24917
|
+
return {
|
|
24918
|
+
validAfter,
|
|
24919
|
+
validBefore
|
|
24920
|
+
};
|
|
24921
|
+
}
|
|
24922
|
+
/**
|
|
24923
|
+
* The interaction-aware signer: derive the bound nonce, assemble the
|
|
24924
|
+
* authorization, and sign it. This is the one piece a stock x402 signer cannot
|
|
24925
|
+
* do (it generates the nonce internally with no injection point), so the payer
|
|
24926
|
+
* side needs this Primitive-provided helper. The key never leaves the caller.
|
|
24927
|
+
*/
|
|
24928
|
+
async function signInteractionPayment(params) {
|
|
24929
|
+
const authorization = {
|
|
24930
|
+
from: getAddress(params.payer),
|
|
24931
|
+
to: getAddress(params.payTo),
|
|
24932
|
+
value: params.amount,
|
|
24933
|
+
validAfter: params.validAfter,
|
|
24934
|
+
validBefore: params.validBefore,
|
|
24935
|
+
nonce: deriveEip3009Nonce(params.nonceBinding)
|
|
24936
|
+
};
|
|
24937
|
+
return {
|
|
24938
|
+
authorization,
|
|
24939
|
+
signature: await params.sign(transferWithAuthorizationTypedData(params.domain, authorization))
|
|
24940
|
+
};
|
|
24941
|
+
}
|
|
24942
|
+
/** The authorization nonce is 32 bytes rendered as a 0x-prefixed 64-char hex string. */
|
|
24943
|
+
const NONCE_HEX_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
24944
|
+
/** A shape-valid EIP signature is 65 bytes (r,s,v) rendered as 130 hex chars. */
|
|
24945
|
+
const SIGNATURE_HEX_RE = /^0x[0-9a-fA-F]{130}$/;
|
|
24946
|
+
/**
|
|
24947
|
+
* Assemble (and validate) the exact-EVM x402 wire payload from an
|
|
24948
|
+
* interaction-bound, locally-signed authorization. The numeric authorization
|
|
24949
|
+
* fields are decimal strings in the wire schema, so the bigints are stringified
|
|
24950
|
+
* here; the nonce passes through as hex. Validation rejects a malformed nonce or
|
|
24951
|
+
* signature loudly rather than emitting a payload the platform will reject.
|
|
24952
|
+
*/
|
|
24953
|
+
function buildExactEvmPaymentPayload(params) {
|
|
24954
|
+
if (params.network !== "base" && params.network !== "base-sepolia") throw new Error(`buildExactEvmPaymentPayload: unsupported network ${params.network}`);
|
|
24955
|
+
if (!SIGNATURE_HEX_RE.test(params.signature)) throw new Error("buildExactEvmPaymentPayload: signature must be a 0x-prefixed 65-byte (130 hex char) EIP signature");
|
|
24956
|
+
if (!NONCE_HEX_RE.test(params.authorization.nonce)) throw new Error("buildExactEvmPaymentPayload: authorization.nonce must be a 0x-prefixed 32-byte (64 hex char) value");
|
|
24957
|
+
return toPaymentPayload(params.network, params.authorization, params.signature);
|
|
24958
|
+
}
|
|
24959
|
+
/**
|
|
24960
|
+
* x402 agent-to-agent payments.
|
|
24961
|
+
*
|
|
24962
|
+
* `charge()` (payee) asks for a payment; `pay()` (payer) signs and settles it
|
|
24963
|
+
* with the customer's own key. The signing is local and non-custodial; the key
|
|
24964
|
+
* never leaves the caller. The server resolves the real payee address, verifies
|
|
24965
|
+
* every signed field against its own records, and enforces the spend policy, so
|
|
24966
|
+
* the SDK's job is just: derive the bound authorization, sign, and submit.
|
|
24967
|
+
*/
|
|
24531
24968
|
const CHAIN_IDS = {
|
|
24532
24969
|
"base-sepolia": 84532,
|
|
24533
24970
|
base: 8453
|
|
24534
24971
|
};
|
|
24535
|
-
const CLOCK_SKEW_SEC = 300;
|
|
24536
|
-
const SETTLEMENT_MARGIN_SEC = 300;
|
|
24537
24972
|
const DEFAULT_BASE_URL = "https://api.primitive.dev";
|
|
24538
24973
|
const CHARGE_INPUT_KEYS = {
|
|
24539
24974
|
amount: true,
|
|
@@ -24545,6 +24980,17 @@ const CHARGE_INPUT_KEYS = {
|
|
|
24545
24980
|
expiresIn: true,
|
|
24546
24981
|
idempotencyKey: true
|
|
24547
24982
|
};
|
|
24983
|
+
const EMAIL_CHARGE_INPUT_KEYS = {
|
|
24984
|
+
from: true,
|
|
24985
|
+
to: true,
|
|
24986
|
+
amount: true,
|
|
24987
|
+
amountUsdc: true,
|
|
24988
|
+
network: true,
|
|
24989
|
+
description: true,
|
|
24990
|
+
resource: true,
|
|
24991
|
+
expiresIn: true,
|
|
24992
|
+
idempotencyKey: true
|
|
24993
|
+
};
|
|
24548
24994
|
function usdcToBaseUnits$1(human) {
|
|
24549
24995
|
const trimmed = human.trim();
|
|
24550
24996
|
if (!/^\d+(\.\d+)?$/.test(trimmed)) return null;
|
|
@@ -24581,13 +25027,37 @@ function validateChallenge(c) {
|
|
|
24581
25027
|
if (!c.expires_at) bad("expires_at");
|
|
24582
25028
|
const nb = c.nonce_binding;
|
|
24583
25029
|
if (!nb?.interaction_id || !nb.challenge_step_id || !nb.challenge_nonce) bad("nonce_binding");
|
|
24584
|
-
|
|
25030
|
+
validatePaymentRequirements(c.payment_requirements, bad);
|
|
25031
|
+
}
|
|
25032
|
+
/** Validate the x402 PaymentRequirements shared by both challenge shapes. */
|
|
25033
|
+
function validatePaymentRequirements(pr, bad) {
|
|
24585
25034
|
if (!pr) bad("payment_requirements");
|
|
24586
|
-
if (
|
|
25035
|
+
if (!/^[1-9][0-9]{0,38}$/.test(pr.maxAmountRequired ?? "")) bad("payment_requirements.maxAmountRequired (expected a positive integer string in token base units)");
|
|
24587
25036
|
if (!/^0x[0-9a-fA-F]{40}$/.test(pr.payTo ?? "")) bad("payment_requirements.payTo (expected a 0x address)");
|
|
24588
25037
|
if (!/^0x[0-9a-fA-F]{40}$/.test(pr.asset ?? "")) bad("payment_requirements.asset (expected a 0x address)");
|
|
24589
25038
|
if (!pr.extra?.name || !pr.extra.version) bad("payment_requirements.extra (name/version)");
|
|
24590
25039
|
}
|
|
25040
|
+
/**
|
|
25041
|
+
* Assert an email-native challenge is fully hydrated before signing, so a
|
|
25042
|
+
* missing field fails with a named X402Error instead of an opaque error
|
|
25043
|
+
* mid-sign. The interaction_id and the challenge step id (the nonce binding's
|
|
25044
|
+
* fields) drive both the bound nonce and the payment-step envelope, so they are
|
|
25045
|
+
* checked here.
|
|
25046
|
+
*/
|
|
25047
|
+
function validateEmailChallenge(c) {
|
|
25048
|
+
const bad = (field) => {
|
|
25049
|
+
throw new X402Error(`email challenge is missing or malformed: ${field}`, 0);
|
|
25050
|
+
};
|
|
25051
|
+
if (!c || typeof c !== "object") bad("email challenge");
|
|
25052
|
+
if (!c.interaction_id) bad("interaction_id");
|
|
25053
|
+
const ch = c.challenge;
|
|
25054
|
+
if (!ch || typeof ch !== "object") bad("challenge");
|
|
25055
|
+
if (!ch.expires_at) bad("challenge.expires_at");
|
|
25056
|
+
const nb = ch.nonce_binding;
|
|
25057
|
+
if (!nb?.interaction_id || !nb.challenge_step_id || !nb.challenge_nonce) bad("challenge.nonce_binding");
|
|
25058
|
+
if (nb.interaction_id !== c.interaction_id) bad("interaction_id (mismatch with challenge.nonce_binding.interaction_id)");
|
|
25059
|
+
validatePaymentRequirements(ch.payment_requirements, bad);
|
|
25060
|
+
}
|
|
24591
25061
|
var X402Client = class {
|
|
24592
25062
|
#apiKey;
|
|
24593
25063
|
#baseUrl;
|
|
@@ -24647,6 +25117,99 @@ var X402Client = class {
|
|
|
24647
25117
|
return this.#request("POST", "/v1/x402/challenges", body, { headers: input.idempotencyKey ? { "idempotency-key": input.idempotencyKey } : void 0 });
|
|
24648
25118
|
}
|
|
24649
25119
|
/**
|
|
25120
|
+
* Issue a payment challenge over an email thread (payee side). Sends the
|
|
25121
|
+
* challenge as an email from `from` to `to` and binds the payment to that
|
|
25122
|
+
* thread. Returns the challenge (including the real `interaction_id`); deliver
|
|
25123
|
+
* it to the payer, who calls `payEmailChallenge` to build the signed payment.
|
|
25124
|
+
*
|
|
25125
|
+
* Provide exactly one of `amount` (base units) or `amountUsdc` (human USDC).
|
|
25126
|
+
*/
|
|
25127
|
+
async createEmailChallenge(input) {
|
|
25128
|
+
for (const key of Object.keys(input)) if (!(key in EMAIL_CHARGE_INPUT_KEYS)) throw new X402Error(`unknown createEmailChallenge() option "${key}"; expected one of: ${Object.keys(EMAIL_CHARGE_INPUT_KEYS).join(", ")}`, 0);
|
|
25129
|
+
if (!input.from) throw new X402Error("createEmailChallenge() requires `from`", 0);
|
|
25130
|
+
if (!input.to) throw new X402Error("createEmailChallenge() requires `to`", 0);
|
|
25131
|
+
if (input.amount !== void 0 && input.amountUsdc !== void 0) throw new X402Error("createEmailChallenge() takes exactly one of `amount` (base units) or `amountUsdc` (human USDC), not both", 0);
|
|
25132
|
+
const amount = input.amountUsdc !== void 0 ? usdcToBaseUnits$1(input.amountUsdc) : input.amount ?? null;
|
|
25133
|
+
if (!amount || !/^[1-9][0-9]{0,38}$/.test(amount)) throw new X402Error("createEmailChallenge() requires `amount` as a positive integer string in token base units (e.g. \"10000\"), or `amountUsdc` as a positive USDC amount with at most 6 decimals (e.g. \"0.01\")", 0);
|
|
25134
|
+
const body = {
|
|
25135
|
+
from: input.from,
|
|
25136
|
+
to: input.to,
|
|
25137
|
+
amount,
|
|
25138
|
+
network: input.network ?? "base-sepolia"
|
|
25139
|
+
};
|
|
25140
|
+
if (input.description) body.description = input.description;
|
|
25141
|
+
if (input.resource) body.resource = input.resource;
|
|
25142
|
+
if (input.expiresIn !== void 0) body.expires_in = input.expiresIn;
|
|
25143
|
+
return this.#request("POST", "/v1/x402/email-challenges", body, { headers: input.idempotencyKey ? { "idempotency-key": input.idempotencyKey } : void 0 });
|
|
25144
|
+
}
|
|
25145
|
+
/**
|
|
25146
|
+
* Build the signed payment step for an email-native challenge (payer side).
|
|
25147
|
+
* Given a received `X402EmailChallenge` and the caller's signer, this derives
|
|
25148
|
+
* the interaction-bound authorization, signs it locally, and returns the
|
|
25149
|
+
* signed `interaction.json` payment-step envelope plus its canonical JSON
|
|
25150
|
+
* bytes. It does NOT send anything.
|
|
25151
|
+
*
|
|
25152
|
+
* The caller sends `result.json` back as an `interaction.json` attachment on a
|
|
25153
|
+
* reply to the challenge email (e.g. via the SDK's `send` / `reply`); the
|
|
25154
|
+
* platform reads the envelope from those exact bytes, re-derives the bound
|
|
25155
|
+
* nonce, and settles.
|
|
25156
|
+
*/
|
|
25157
|
+
async payEmailChallenge(challenge, options) {
|
|
25158
|
+
if (!options?.signer?.address || typeof options.signer.signTypedData !== "function") throw new X402Error("payEmailChallenge() requires options.signer with { address, signTypedData } (e.g. a viem LocalAccount)", 0);
|
|
25159
|
+
validateEmailChallenge(challenge);
|
|
25160
|
+
const details = challenge.challenge;
|
|
25161
|
+
const pr = details.payment_requirements;
|
|
25162
|
+
const network = pr.network;
|
|
25163
|
+
const chainId = CHAIN_IDS[network];
|
|
25164
|
+
if (chainId === void 0) throw new X402Error(`unsupported network: ${network}`, 0);
|
|
25165
|
+
if (pr.scheme !== "exact") throw new X402Error(`unsupported payment scheme: ${pr.scheme}`, 0);
|
|
25166
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
25167
|
+
const expiresAtMs = Date.parse(details.expires_at);
|
|
25168
|
+
if (Number.isNaN(expiresAtMs)) throw new X402Error(`challenge has an invalid expires_at: ${details.expires_at}`, 0);
|
|
25169
|
+
const expiresAtSec = Math.floor(expiresAtMs / 1e3);
|
|
25170
|
+
if (expiresAtSec <= nowSec) throw new X402Error(`challenge has already expired (expires_at ${details.expires_at}); not signing`, 0);
|
|
25171
|
+
let validAfter;
|
|
25172
|
+
let validBefore;
|
|
25173
|
+
try {
|
|
25174
|
+
({validAfter, validBefore} = computePaymentValidityWindow({
|
|
25175
|
+
challengeExpiresAtSec: expiresAtSec,
|
|
25176
|
+
nowSec
|
|
25177
|
+
}));
|
|
25178
|
+
} catch (cause) {
|
|
25179
|
+
throw new X402Error(cause instanceof Error ? cause.message : String(cause), 0, void 0, { cause });
|
|
25180
|
+
}
|
|
25181
|
+
const { authorization, signature } = await signInteractionPayment({
|
|
25182
|
+
sign: (typedData) => options.signer.signTypedData(typedData),
|
|
25183
|
+
payer: options.signer.address,
|
|
25184
|
+
domain: {
|
|
25185
|
+
name: pr.extra.name,
|
|
25186
|
+
version: pr.extra.version,
|
|
25187
|
+
chainId,
|
|
25188
|
+
verifyingContract: pr.asset
|
|
25189
|
+
},
|
|
25190
|
+
payTo: pr.payTo,
|
|
25191
|
+
amount: BigInt(pr.maxAmountRequired),
|
|
25192
|
+
nonceBinding: {
|
|
25193
|
+
interactionId: details.nonce_binding.interaction_id,
|
|
25194
|
+
challengeStepId: details.nonce_binding.challenge_step_id,
|
|
25195
|
+
challengeNonce: details.nonce_binding.challenge_nonce
|
|
25196
|
+
},
|
|
25197
|
+
validAfter,
|
|
25198
|
+
validBefore
|
|
25199
|
+
});
|
|
25200
|
+
const payment = buildExactEvmPaymentPayload({
|
|
25201
|
+
network,
|
|
25202
|
+
authorization,
|
|
25203
|
+
signature
|
|
25204
|
+
});
|
|
25205
|
+
return buildPaymentStepEnvelope({
|
|
25206
|
+
interactionId: challenge.interaction_id,
|
|
25207
|
+
stepId: randomUUID(),
|
|
25208
|
+
prevStepId: details.nonce_binding.challenge_step_id,
|
|
25209
|
+
payment
|
|
25210
|
+
});
|
|
25211
|
+
}
|
|
25212
|
+
/**
|
|
24650
25213
|
* Pay a challenge (payer side). Derives the interaction-bound authorization,
|
|
24651
25214
|
* signs it locally with the caller's key, and submits it for settlement.
|
|
24652
25215
|
*/
|
|
@@ -24658,33 +25221,45 @@ var X402Client = class {
|
|
|
24658
25221
|
const pr = challenge.payment_requirements;
|
|
24659
25222
|
if (pr.network !== challenge.network) throw new X402Error(`challenge network mismatch: ${challenge.network} vs payment_requirements ${pr.network}`, 0);
|
|
24660
25223
|
if (pr.scheme !== "exact") throw new X402Error(`unsupported payment scheme: ${pr.scheme}`, 0);
|
|
24661
|
-
const nonce = deriveEip3009Nonce({
|
|
24662
|
-
interactionId: challenge.nonce_binding.interaction_id,
|
|
24663
|
-
challengeStepId: challenge.nonce_binding.challenge_step_id,
|
|
24664
|
-
challengeNonce: challenge.nonce_binding.challenge_nonce
|
|
24665
|
-
});
|
|
24666
25224
|
const nowSec = Math.floor(Date.now() / 1e3);
|
|
24667
25225
|
const expiresAtMs = Date.parse(challenge.expires_at);
|
|
24668
25226
|
if (Number.isNaN(expiresAtMs)) throw new X402Error(`challenge has an invalid expires_at: ${challenge.expires_at}`, 0);
|
|
24669
25227
|
const expiresAtSec = Math.floor(expiresAtMs / 1e3);
|
|
24670
25228
|
if (expiresAtSec <= nowSec) throw new X402Error(`challenge has already expired (expires_at ${challenge.expires_at}); not signing`, 0);
|
|
24671
|
-
|
|
24672
|
-
|
|
24673
|
-
|
|
24674
|
-
|
|
24675
|
-
|
|
24676
|
-
|
|
25229
|
+
let validAfter;
|
|
25230
|
+
let validBefore;
|
|
25231
|
+
try {
|
|
25232
|
+
({validAfter, validBefore} = computePaymentValidityWindow({
|
|
25233
|
+
challengeExpiresAtSec: expiresAtSec,
|
|
25234
|
+
nowSec
|
|
25235
|
+
}));
|
|
25236
|
+
} catch (cause) {
|
|
25237
|
+
throw new X402Error(cause instanceof Error ? cause.message : String(cause), 0, void 0, { cause });
|
|
25238
|
+
}
|
|
25239
|
+
const { authorization, signature } = await signInteractionPayment({
|
|
25240
|
+
sign: (typedData) => options.signer.signTypedData(typedData),
|
|
25241
|
+
payer: options.signer.address,
|
|
25242
|
+
domain: {
|
|
25243
|
+
name: pr.extra.name,
|
|
25244
|
+
version: pr.extra.version,
|
|
25245
|
+
chainId,
|
|
25246
|
+
verifyingContract: pr.asset
|
|
25247
|
+
},
|
|
25248
|
+
payTo: pr.payTo,
|
|
25249
|
+
amount: BigInt(pr.maxAmountRequired),
|
|
25250
|
+
nonceBinding: {
|
|
25251
|
+
interactionId: challenge.nonce_binding.interaction_id,
|
|
25252
|
+
challengeStepId: challenge.nonce_binding.challenge_step_id,
|
|
25253
|
+
challengeNonce: challenge.nonce_binding.challenge_nonce
|
|
25254
|
+
},
|
|
24677
25255
|
validAfter,
|
|
24678
|
-
validBefore
|
|
24679
|
-
|
|
24680
|
-
}
|
|
24681
|
-
|
|
24682
|
-
|
|
24683
|
-
|
|
24684
|
-
|
|
24685
|
-
verifyingContract: pr.asset
|
|
24686
|
-
}, auth));
|
|
24687
|
-
return this.#request("POST", `/v1/x402/challenges/${challenge.id}/pay`, { payment: toPaymentPayload(challenge.network, auth, signature) });
|
|
25256
|
+
validBefore
|
|
25257
|
+
});
|
|
25258
|
+
return this.#request("POST", `/v1/x402/challenges/${challenge.id}/pay`, { payment: buildExactEvmPaymentPayload({
|
|
25259
|
+
network: challenge.network,
|
|
25260
|
+
authorization,
|
|
25261
|
+
signature
|
|
25262
|
+
}) });
|
|
24688
25263
|
}
|
|
24689
25264
|
/** Fetch a challenge by id (scoped to the challenger org that created it). */
|
|
24690
25265
|
async getChallenge(id) {
|
|
@@ -25054,6 +25629,116 @@ var PaymentsPayCommand = class PaymentsPayCommand extends Command {
|
|
|
25054
25629
|
}
|
|
25055
25630
|
};
|
|
25056
25631
|
//#endregion
|
|
25632
|
+
//#region src/oclif/commands/payments-pay-email-step.ts
|
|
25633
|
+
function readEmailChallenge(input) {
|
|
25634
|
+
let raw;
|
|
25635
|
+
if (input.inline !== void 0) raw = input.inline;
|
|
25636
|
+
else if (input.file !== void 0) raw = readFileSync(input.file, "utf8");
|
|
25637
|
+
else {
|
|
25638
|
+
if (process.stdin.isTTY) process.stderr.write("Reading the email challenge JSON from stdin; paste it and press Ctrl-D (or use --challenge / --challenge-file).\n");
|
|
25639
|
+
raw = readFileSync(0, "utf8");
|
|
25640
|
+
}
|
|
25641
|
+
const trimmed = raw.trim();
|
|
25642
|
+
if (!trimmed) throw new Error("no challenge provided; pass --challenge '<json>', --challenge-file <path>, or pipe the email challenge JSON on stdin");
|
|
25643
|
+
let parsed;
|
|
25644
|
+
try {
|
|
25645
|
+
parsed = JSON.parse(trimmed);
|
|
25646
|
+
} catch {
|
|
25647
|
+
throw new Error("challenge is not valid JSON");
|
|
25648
|
+
}
|
|
25649
|
+
if (parsed && typeof parsed === "object" && "data" in parsed) {
|
|
25650
|
+
const data = parsed.data;
|
|
25651
|
+
if (!data || typeof data !== "object") throw new Error("challenge envelope has no `data` object; pass the email challenge JSON itself or a `{ \"data\": { ... } }` envelope");
|
|
25652
|
+
return data;
|
|
25653
|
+
}
|
|
25654
|
+
return parsed;
|
|
25655
|
+
}
|
|
25656
|
+
var PaymentsPayEmailStepCommand = class PaymentsPayEmailStepCommand extends Command {
|
|
25657
|
+
static description = `Sign an email-native x402 challenge into a payment-step interaction.json.
|
|
25658
|
+
|
|
25659
|
+
Reads the email challenge the payee issued (the JSON from
|
|
25660
|
+
\`payments create-email-challenge\`), derives and signs the interaction-bound
|
|
25661
|
+
EIP-3009 authorization locally with your wallet key (read from
|
|
25662
|
+
${PRIVATE_KEY_ENV} by default), and prints the signed \`interaction.json\`
|
|
25663
|
+
payment-step envelope. The key never leaves your machine.
|
|
25664
|
+
|
|
25665
|
+
This command does NOT send the payment. Attach the printed envelope as the
|
|
25666
|
+
\`interaction.json\` part of your reply to the challenge email, for example:
|
|
25667
|
+
primitive payments pay-email-step --challenge-file challenge.json > interaction.json
|
|
25668
|
+
primitive reply --attachment interaction.json
|
|
25669
|
+
|
|
25670
|
+
Provide the challenge inline with --challenge, from a file with
|
|
25671
|
+
--challenge-file, or piped on stdin.`;
|
|
25672
|
+
static summary = "Sign an email x402 challenge into a payment-step envelope";
|
|
25673
|
+
static examples = [
|
|
25674
|
+
"<%= config.bin %> payments pay-email-step --challenge-file challenge.json > interaction.json",
|
|
25675
|
+
"<%= config.bin %> payments pay-email-step --challenge '{\"interaction_id\":\"...\",\"challenge\":{...}}'",
|
|
25676
|
+
"cat challenge.json | <%= config.bin %> payments pay-email-step"
|
|
25677
|
+
];
|
|
25678
|
+
static flags = {
|
|
25679
|
+
"api-key": Flags.string({
|
|
25680
|
+
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
25681
|
+
env: "PRIMITIVE_API_KEY"
|
|
25682
|
+
}),
|
|
25683
|
+
"api-base-url": Flags.string({
|
|
25684
|
+
description: API_BASE_URL_FLAG_DESCRIPTION,
|
|
25685
|
+
env: "PRIMITIVE_API_BASE_URL",
|
|
25686
|
+
hidden: true
|
|
25687
|
+
}),
|
|
25688
|
+
"private-key": Flags.string({
|
|
25689
|
+
description: PRIVATE_KEY_FLAG_DESCRIPTION,
|
|
25690
|
+
env: PRIVATE_KEY_ENV
|
|
25691
|
+
}),
|
|
25692
|
+
challenge: Flags.string({
|
|
25693
|
+
description: "The email challenge object as a JSON string.",
|
|
25694
|
+
exclusive: ["challenge-file"]
|
|
25695
|
+
}),
|
|
25696
|
+
"challenge-file": Flags.string({
|
|
25697
|
+
description: "Path to a file containing the email challenge JSON.",
|
|
25698
|
+
exclusive: ["challenge"]
|
|
25699
|
+
}),
|
|
25700
|
+
json: Flags.boolean({ description: "Print the full signed envelope object (default prints the interaction.json bytes)." }),
|
|
25701
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
25702
|
+
};
|
|
25703
|
+
async run() {
|
|
25704
|
+
const { flags } = await this.parse(PaymentsPayEmailStepCommand);
|
|
25705
|
+
const requestConfig = resolveCliApiRequestConfig({
|
|
25706
|
+
apiBaseUrl: flags["api-base-url"],
|
|
25707
|
+
configDir: this.config.configDir
|
|
25708
|
+
});
|
|
25709
|
+
const baseUrlOverridden = requestConfig.baseUrlOverridden;
|
|
25710
|
+
const auth = {
|
|
25711
|
+
apiKey: flags["api-key"],
|
|
25712
|
+
apiBaseUrl: requestConfig.resolvedApiBaseUrl,
|
|
25713
|
+
source: "none",
|
|
25714
|
+
credentials: null
|
|
25715
|
+
};
|
|
25716
|
+
await runWithTiming(flags.time, async () => {
|
|
25717
|
+
const client = createX402Client({
|
|
25718
|
+
apiKey: flags["api-key"] || void 0,
|
|
25719
|
+
baseUrl: x402BaseUrl(requestConfig.resolvedApiBaseUrl)
|
|
25720
|
+
});
|
|
25721
|
+
try {
|
|
25722
|
+
const challenge = readEmailChallenge({
|
|
25723
|
+
inline: flags.challenge,
|
|
25724
|
+
file: flags["challenge-file"]
|
|
25725
|
+
});
|
|
25726
|
+
const signer = signerFromPrivateKey(flags["private-key"] ?? "");
|
|
25727
|
+
const built = await client.payEmailChallenge(challenge, { signer });
|
|
25728
|
+
if (flags.json) this.log(JSON.stringify(built.envelope, null, 2));
|
|
25729
|
+
else this.log(built.json);
|
|
25730
|
+
} catch (error) {
|
|
25731
|
+
reportX402Error(error, {
|
|
25732
|
+
auth,
|
|
25733
|
+
baseUrlOverridden,
|
|
25734
|
+
configDir: this.config.configDir
|
|
25735
|
+
});
|
|
25736
|
+
process.exitCode = 1;
|
|
25737
|
+
}
|
|
25738
|
+
});
|
|
25739
|
+
}
|
|
25740
|
+
};
|
|
25741
|
+
//#endregion
|
|
25057
25742
|
//#region src/oclif/commands/payments-register-payout-address.ts
|
|
25058
25743
|
var PaymentsRegisterPayoutAddressCommand = class PaymentsRegisterPayoutAddressCommand extends Command {
|
|
25059
25744
|
static description = `Register the default payout address your org receives x402 payments at.
|
|
@@ -26653,7 +27338,8 @@ const COMMANDS = {
|
|
|
26653
27338
|
"payments:register-payout": PaymentsRegisterPayoutAddressCommand,
|
|
26654
27339
|
"payments:charge": PaymentsChargeCommand,
|
|
26655
27340
|
"payments:pay-challenge": PaymentsPayCommand,
|
|
26656
|
-
"payments:pay": PaymentsPayCommand
|
|
27341
|
+
"payments:pay": PaymentsPayCommand,
|
|
27342
|
+
"payments:pay-email-step": PaymentsPayEmailStepCommand
|
|
26657
27343
|
};
|
|
26658
27344
|
//#endregion
|
|
26659
27345
|
export { CANONICAL_OPERATION_ALIASES, COMMANDS, lookupOperation };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primcli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|