@canister-software/x402-icp 0.1.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/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/scheme.d.ts +40 -0
- package/dist/client/scheme.js +69 -0
- package/dist/client/scheme.js.map +1 -0
- package/dist/client/signer.d.ts +89 -0
- package/dist/client/signer.js +186 -0
- package/dist/client/signer.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/requirements.d.ts +22 -0
- package/dist/server/requirements.js +19 -0
- package/dist/server/requirements.js.map +1 -0
- package/dist/server/scheme.d.ts +22 -0
- package/dist/server/scheme.js +51 -0
- package/dist/server/scheme.js.map +1 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/index.js +24 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Canister Software Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @canister-software/x402-icp
|
|
2
|
+
|
|
3
|
+
[x402](https://x402.org) payment scheme for the [Internet Computer](https://internetcomputer.org) — brings native ICP token payments (ICP, ckUSDC, ckUSDT) to the HTTP 402 standard using ICRC-2 approve/transferFrom.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
x402 is an open protocol for machine-native HTTP payments. This library adds ICP support to the x402 ecosystem, letting any HTTP resource accept ICP tokens with a single line of middleware — and any client pay automatically using an internet identity.
|
|
8
|
+
|
|
9
|
+
**Supported assets**
|
|
10
|
+
|
|
11
|
+
| Symbol | Asset |
|
|
12
|
+
|--------|---------|
|
|
13
|
+
| ICP | `icp:1:ryjl3-tyaaa-aaaaa-aaaba-cai` |
|
|
14
|
+
| ckUSDC | `icp:1:xevnm-gaaaa-aaaar-qafnq-cai` |
|
|
15
|
+
| ckUSDT | `icp:1:cngnf-vqaaa-aaaar-qag4q-cai` |
|
|
16
|
+
|
|
17
|
+
`icp:1` signifisy the network as
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @canister-software/x402-icp
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Requires `@x402/core >= 2.4.0` as a peer dependency.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
### Client — paying for a resource
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { x402Client } from '@x402/core/client'
|
|
33
|
+
import { wrapFetchWithPayment } from '@x402/fetch'
|
|
34
|
+
import { registerExactIcpScheme, createPemSigner } from '@canister-software/x402-icp/client'
|
|
35
|
+
import fs from 'fs'
|
|
36
|
+
|
|
37
|
+
const signer = await createPemSigner({
|
|
38
|
+
pem: fs.readFileSync('identity.pem', 'utf8'),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const client = new x402Client()
|
|
42
|
+
registerExactIcpScheme(client, { signer })
|
|
43
|
+
|
|
44
|
+
const fetch402 = wrapFetchWithPayment(fetch, client)
|
|
45
|
+
const res = await fetch402('https://api.example.com/premium')
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Server — protecting a resource
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { createIcpPaymentRequirements } from '@canister-software/x402-icp/server'
|
|
52
|
+
|
|
53
|
+
const requirements = createIcpPaymentRequirements({
|
|
54
|
+
network: 'icp:1:xevnm-gaaaa-aaaar-qafnq-cai', // ckUSDC
|
|
55
|
+
amount: '1000000', // 1 ckUSDC (6 decimals)
|
|
56
|
+
payTo: 'your-principal-id',
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Facilitator
|
|
61
|
+
|
|
62
|
+
A public facilitator for ICP is operated by Canister Software at:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
https://facilitator.consensus.canister.software
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Full documentation: [docs.canister.software](https://docs.canister.software)
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,oBAAoB,EACpB,WAAW,GACZ,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { IcpPayload } from '../types/index.js';
|
|
2
|
+
import type { IcpSigner } from './signer.js';
|
|
3
|
+
/**
|
|
4
|
+
* x402 PaymentRequirements as received from the 402 response.
|
|
5
|
+
* We define locally to avoid hard coupling to @x402/core version.
|
|
6
|
+
*/
|
|
7
|
+
interface PaymentRequirements {
|
|
8
|
+
scheme: string;
|
|
9
|
+
network: string;
|
|
10
|
+
amount: string;
|
|
11
|
+
asset: string;
|
|
12
|
+
payTo: string;
|
|
13
|
+
maxTimeoutSeconds: number;
|
|
14
|
+
extra?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* ExactIcpClient implements SchemeNetworkClient for ICP.
|
|
18
|
+
*
|
|
19
|
+
* When @x402/fetch receives a 402 with network "icp:*", it calls
|
|
20
|
+
* createPaymentPayload() which:
|
|
21
|
+
* 1. Calls icrc2_approve on the ledger (authorize facilitator to spend)
|
|
22
|
+
* 2. Signs the authorization object
|
|
23
|
+
* 3. Returns the payload for the x-payment header
|
|
24
|
+
*/
|
|
25
|
+
export declare class ExactIcpClient {
|
|
26
|
+
readonly scheme = "exact";
|
|
27
|
+
private signer;
|
|
28
|
+
constructor(signer: IcpSigner);
|
|
29
|
+
/**
|
|
30
|
+
* Create a payment payload from 402 payment requirements.
|
|
31
|
+
* Called by x402Client when it matches an icp:* network.
|
|
32
|
+
*
|
|
33
|
+
* Signature matches SchemeNetworkClient: (x402Version, requirements) => payload
|
|
34
|
+
*/
|
|
35
|
+
createPaymentPayload(x402Version: number, requirements: PaymentRequirements): Promise<{
|
|
36
|
+
x402Version: number;
|
|
37
|
+
payload: IcpPayload;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ASSETS } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* ExactIcpClient implements SchemeNetworkClient for ICP.
|
|
4
|
+
*
|
|
5
|
+
* When @x402/fetch receives a 402 with network "icp:*", it calls
|
|
6
|
+
* createPaymentPayload() which:
|
|
7
|
+
* 1. Calls icrc2_approve on the ledger (authorize facilitator to spend)
|
|
8
|
+
* 2. Signs the authorization object
|
|
9
|
+
* 3. Returns the payload for the x-payment header
|
|
10
|
+
*/
|
|
11
|
+
export class ExactIcpClient {
|
|
12
|
+
scheme = 'exact';
|
|
13
|
+
signer;
|
|
14
|
+
constructor(signer) {
|
|
15
|
+
this.signer = signer;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create a payment payload from 402 payment requirements.
|
|
19
|
+
* Called by x402Client when it matches an icp:* network.
|
|
20
|
+
*
|
|
21
|
+
* Signature matches SchemeNetworkClient: (x402Version, requirements) => payload
|
|
22
|
+
*/
|
|
23
|
+
async createPaymentPayload(x402Version, requirements) {
|
|
24
|
+
// Extract ledger canister ID from network: icp:1:<canisterId>
|
|
25
|
+
const network = requirements.network;
|
|
26
|
+
if (!network) {
|
|
27
|
+
throw new Error(`[ExactIcpClient] requirements.network is undefined.`);
|
|
28
|
+
}
|
|
29
|
+
const ledgerId = network.split(':').pop();
|
|
30
|
+
const asset = ASSETS[ledgerId];
|
|
31
|
+
if (!asset) {
|
|
32
|
+
throw new Error(`Unknown ICP asset for network: ${network}`);
|
|
33
|
+
}
|
|
34
|
+
// Get facilitator principal from requirements.extra
|
|
35
|
+
const facilitatorPrincipal = requirements.extra?.facilitatorPrincipal;
|
|
36
|
+
if (!facilitatorPrincipal) {
|
|
37
|
+
throw new Error('Missing facilitatorPrincipal in payment requirements extra. ' +
|
|
38
|
+
'The resource server must include this from the facilitator.');
|
|
39
|
+
}
|
|
40
|
+
const amount = BigInt(requirements.amount);
|
|
41
|
+
const nonce = Date.now();
|
|
42
|
+
const expiresAt = Date.now() + (requirements.maxTimeoutSeconds * 1000);
|
|
43
|
+
// Step 1: Approve the facilitator to spend our tokens via ICRC-2
|
|
44
|
+
await this.signer.icrc2Approve({
|
|
45
|
+
ledgerCanisterId: ledgerId,
|
|
46
|
+
spender: facilitatorPrincipal,
|
|
47
|
+
amount: amount + BigInt(asset.transferFee),
|
|
48
|
+
expiresAt: BigInt(expiresAt) * 1000000n,
|
|
49
|
+
});
|
|
50
|
+
// Step 2: Build the authorization object (all JSON-safe types)
|
|
51
|
+
const authorization = {
|
|
52
|
+
to: String(requirements.payTo),
|
|
53
|
+
value: String(requirements.amount),
|
|
54
|
+
expiresAt: Number(expiresAt),
|
|
55
|
+
nonce: Number(nonce),
|
|
56
|
+
};
|
|
57
|
+
// Step 3: Sign the authorization
|
|
58
|
+
const signature = await this.signer.signAuthorization(authorization);
|
|
59
|
+
// Return only JSON-safe payload — no BigInts
|
|
60
|
+
return {
|
|
61
|
+
x402Version,
|
|
62
|
+
payload: {
|
|
63
|
+
signature,
|
|
64
|
+
authorization,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=scheme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheme.js","sourceRoot":"","sources":["../../src/client/scheme.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAiB1C;;;;;;;;GAQG;AACH,MAAM,OAAO,cAAc;IAChB,MAAM,GAAG,OAAO,CAAA;IACjB,MAAM,CAAW;IAEzB,YAAY,MAAiB;QAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,oBAAoB,CACxB,WAAmB,EACnB,YAAiC;QAEjC,8DAA8D;QAC9D,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAA;QACpC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAA;QACxE,CAAC;QACD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAA;QAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC9B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,kCAAkC,OAAO,EAAE,CAAC,CAAA;QAC9D,CAAC;QAED,oDAAoD;QACpD,MAAM,oBAAoB,GAAG,YAAY,CAAC,KAAK,EAAE,oBAA8B,CAAA;QAC/E,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CACb,8DAA8D;gBAC9D,6DAA6D,CAC9D,CAAA;QACH,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAA;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACxB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,YAAY,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAA;QAEtE,iEAAiE;QACjE,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC;YAC7B,gBAAgB,EAAE,QAAQ;YAC1B,OAAO,EAAE,oBAAoB;YAC7B,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC;YAC1C,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,GAAG,QAAU;SAC1C,CAAC,CAAA;QAEF,+DAA+D;QAC/D,MAAM,aAAa,GAA4B;YAC7C,EAAE,EAAE,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC;YAC9B,KAAK,EAAE,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC;YAClC,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC;YAC5B,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC;SACrB,CAAA;QAED,iCAAiC;QACjC,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAA;QAEpE,6CAA6C;QAC7C,OAAO;YACL,WAAW;YACX,OAAO,EAAE;gBACP,SAAS;gBACT,aAAa;aACd;SACF,CAAA;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { IcpPayloadAuthorization } from '../types/index.js';
|
|
2
|
+
export interface Icrc2ApproveParams {
|
|
3
|
+
ledgerCanisterId: string;
|
|
4
|
+
spender: string;
|
|
5
|
+
amount: bigint;
|
|
6
|
+
expiresAt?: bigint;
|
|
7
|
+
}
|
|
8
|
+
export interface IcpSigner {
|
|
9
|
+
/** The principal ID of this signer */
|
|
10
|
+
readonly principal: string;
|
|
11
|
+
/** Call icrc2_approve on a ledger canister */
|
|
12
|
+
icrc2Approve(params: Icrc2ApproveParams): Promise<bigint>;
|
|
13
|
+
/** Sign an authorization object, return base64url-encoded CBOR envelope */
|
|
14
|
+
signAuthorization(auth: IcpPayloadAuthorization): Promise<string>;
|
|
15
|
+
}
|
|
16
|
+
export interface PemSignerOptions {
|
|
17
|
+
/** PEM file contents (string) — as exported by dfx */
|
|
18
|
+
pem: string;
|
|
19
|
+
/** IC host URL (default: https://icp-api.io) */
|
|
20
|
+
host?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Create an ICP signer from a PEM file (as used by dfx identity export).
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* import { createPemSigner } from '@canister-software/x402-icp/client'
|
|
28
|
+
* import fs from 'fs'
|
|
29
|
+
*
|
|
30
|
+
* const pem = fs.readFileSync('identity.pem', 'utf8')
|
|
31
|
+
* const signer = await createPemSigner({ pem })
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function createPemSigner(opts: PemSignerOptions): Promise<IcpSigner>;
|
|
35
|
+
export interface IdentitySignerOptions {
|
|
36
|
+
/** Any @dfinity/identity Identity implementation */
|
|
37
|
+
identity: any;
|
|
38
|
+
/** IC host URL (default: https://icp-api.io) */
|
|
39
|
+
host?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create an ICP signer from an existing @dfinity/identity.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* import { createIdentitySigner } from '@canister-software/x402-icp/client'
|
|
47
|
+
* import { Ed25519KeyIdentity } from '@dfinity/identity'
|
|
48
|
+
*
|
|
49
|
+
* const identity = Ed25519KeyIdentity.generate()
|
|
50
|
+
* const signer = await createIdentitySigner({ identity })
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function createIdentitySigner(opts: IdentitySignerOptions): Promise<IcpSigner>;
|
|
54
|
+
interface X402ClientLike {
|
|
55
|
+
register(network: string, scheme: any): X402ClientLike;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Register the ICP exact payment scheme with an x402Client.
|
|
59
|
+
*
|
|
60
|
+
* Mirrors registerExactEvmScheme(client, { signer }) from @x402/evm.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* import { x402Client } from '@x402/core/client'
|
|
65
|
+
* import { wrapFetchWithPayment } from '@x402/fetch'
|
|
66
|
+
* import { registerExactIcpScheme, createPemSigner } from '@canister-software/x402-icp/client'
|
|
67
|
+
* import fs from 'fs'
|
|
68
|
+
*
|
|
69
|
+
* const signer = await createPemSigner({ pem: fs.readFileSync('identity.pem', 'utf8') })
|
|
70
|
+
* const client = new x402Client()
|
|
71
|
+
* registerExactIcpScheme(client, { signer })
|
|
72
|
+
* const fetchWithPayment = wrapFetchWithPayment(fetch, client)
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export declare function registerExactIcpScheme(client: X402ClientLike, opts: {
|
|
76
|
+
signer: IcpSigner;
|
|
77
|
+
}): X402ClientLike;
|
|
78
|
+
/**
|
|
79
|
+
* Convenience function to create a signer from a PEM file path.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* import { pemToSigner } from '@canister-software/x402-icp/client'
|
|
84
|
+
*
|
|
85
|
+
* const signer = await pemToSigner('~/.config/dfx/identity/default/identity.pem')
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare function pemToSigner(pemPath: string, host?: string): Promise<IcpSigner>;
|
|
89
|
+
export {};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { ExactIcpClient } from './scheme.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create an ICP signer from a PEM file (as used by dfx identity export).
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* import { createPemSigner } from '@canister-software/x402-icp/client'
|
|
8
|
+
* import fs from 'fs'
|
|
9
|
+
*
|
|
10
|
+
* const pem = fs.readFileSync('identity.pem', 'utf8')
|
|
11
|
+
* const signer = await createPemSigner({ pem })
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export async function createPemSigner(opts) {
|
|
15
|
+
const { Ed25519KeyIdentity } = await import('@dfinity/identity');
|
|
16
|
+
// Parse PEM to DER bytes
|
|
17
|
+
const pemBody = opts.pem
|
|
18
|
+
.replace(/-----BEGIN[^-]*-----/, '')
|
|
19
|
+
.replace(/-----END[^-]*-----/, '')
|
|
20
|
+
.replace(/\s/g, '');
|
|
21
|
+
const derBuf = Buffer.from(pemBody, 'base64');
|
|
22
|
+
const derBytes = new Uint8Array(derBuf.buffer, derBuf.byteOffset, derBuf.byteLength);
|
|
23
|
+
let identity;
|
|
24
|
+
// Try Ed25519 first — extract last 32 bytes as seed
|
|
25
|
+
try {
|
|
26
|
+
const seed = derBytes.slice(derBytes.length - 32);
|
|
27
|
+
identity = Ed25519KeyIdentity.generate(seed);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Fall back to Secp256k1
|
|
31
|
+
try {
|
|
32
|
+
const { Secp256k1KeyIdentity } = await import('@dfinity/identity-secp256k1');
|
|
33
|
+
identity = Secp256k1KeyIdentity.fromSecretKey(derBytes.buffer);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new Error('Could not parse PEM as Ed25519 or Secp256k1. ' +
|
|
37
|
+
'For Secp256k1, install: npm install @dfinity/identity-secp256k1');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return createIdentitySigner({ identity, host: opts.host });
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create an ICP signer from an existing @dfinity/identity.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* import { createIdentitySigner } from '@canister-software/x402-icp/client'
|
|
48
|
+
* import { Ed25519KeyIdentity } from '@dfinity/identity'
|
|
49
|
+
*
|
|
50
|
+
* const identity = Ed25519KeyIdentity.generate()
|
|
51
|
+
* const signer = await createIdentitySigner({ identity })
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export async function createIdentitySigner(opts) {
|
|
55
|
+
const { HttpAgent } = await import('@dfinity/agent');
|
|
56
|
+
const { Principal } = await import('@dfinity/principal');
|
|
57
|
+
const { IDL } = await import('@dfinity/candid');
|
|
58
|
+
const host = opts.host ?? 'https://icp-api.io';
|
|
59
|
+
const identity = opts.identity;
|
|
60
|
+
const agent = await HttpAgent.create({ identity, host });
|
|
61
|
+
// Fetch root key for local dev
|
|
62
|
+
if (host.includes('127.0.0.1') || host.includes('localhost')) {
|
|
63
|
+
await agent.fetchRootKey();
|
|
64
|
+
}
|
|
65
|
+
const principal = identity.getPrincipal().toText();
|
|
66
|
+
return {
|
|
67
|
+
principal,
|
|
68
|
+
async icrc2Approve(params) {
|
|
69
|
+
const { Actor } = await import('@dfinity/agent');
|
|
70
|
+
const icrc2Interface = IDL.Service({
|
|
71
|
+
icrc2_approve: IDL.Func([
|
|
72
|
+
IDL.Record({
|
|
73
|
+
fee: IDL.Opt(IDL.Nat),
|
|
74
|
+
memo: IDL.Opt(IDL.Vec(IDL.Nat8)),
|
|
75
|
+
from_subaccount: IDL.Opt(IDL.Vec(IDL.Nat8)),
|
|
76
|
+
created_at_time: IDL.Opt(IDL.Nat64),
|
|
77
|
+
amount: IDL.Nat,
|
|
78
|
+
expected_allowance: IDL.Opt(IDL.Nat),
|
|
79
|
+
expires_at: IDL.Opt(IDL.Nat64),
|
|
80
|
+
spender: IDL.Record({
|
|
81
|
+
owner: IDL.Principal,
|
|
82
|
+
subaccount: IDL.Opt(IDL.Vec(IDL.Nat8)),
|
|
83
|
+
}),
|
|
84
|
+
}),
|
|
85
|
+
], [
|
|
86
|
+
IDL.Variant({
|
|
87
|
+
Ok: IDL.Nat,
|
|
88
|
+
Err: IDL.Variant({
|
|
89
|
+
GenericError: IDL.Record({ message: IDL.Text, error_code: IDL.Nat }),
|
|
90
|
+
TemporarilyUnavailable: IDL.Null,
|
|
91
|
+
Duplicate: IDL.Record({ duplicate_of: IDL.Nat }),
|
|
92
|
+
BadFee: IDL.Record({ expected_fee: IDL.Nat }),
|
|
93
|
+
AllowanceChanged: IDL.Record({ current_allowance: IDL.Nat }),
|
|
94
|
+
CreatedInFuture: IDL.Record({ ledger_time: IDL.Nat64 }),
|
|
95
|
+
TooOld: IDL.Null,
|
|
96
|
+
Expired: IDL.Record({ ledger_time: IDL.Nat64 }),
|
|
97
|
+
InsufficientFunds: IDL.Record({ balance: IDL.Nat }),
|
|
98
|
+
}),
|
|
99
|
+
}),
|
|
100
|
+
], []),
|
|
101
|
+
});
|
|
102
|
+
const ledger = Actor.createActor(() => icrc2Interface, {
|
|
103
|
+
agent,
|
|
104
|
+
canisterId: params.ledgerCanisterId,
|
|
105
|
+
});
|
|
106
|
+
const result = await ledger.icrc2_approve({
|
|
107
|
+
fee: [],
|
|
108
|
+
memo: [],
|
|
109
|
+
from_subaccount: [],
|
|
110
|
+
created_at_time: [],
|
|
111
|
+
amount: params.amount,
|
|
112
|
+
expected_allowance: [],
|
|
113
|
+
expires_at: params.expiresAt !== undefined ? [params.expiresAt] : [],
|
|
114
|
+
spender: {
|
|
115
|
+
owner: Principal.fromText(params.spender),
|
|
116
|
+
subaccount: [],
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
if ('Err' in result) {
|
|
120
|
+
const errKey = Object.keys(result.Err)[0];
|
|
121
|
+
const errVal = result.Err[errKey];
|
|
122
|
+
throw new Error(`ICRC-2 approve failed: ${errKey} - ${JSON.stringify(errVal, (_k, v) => typeof v === 'bigint' ? v.toString() : v)}`);
|
|
123
|
+
}
|
|
124
|
+
return BigInt(result.Ok.toString());
|
|
125
|
+
},
|
|
126
|
+
async signAuthorization(auth) {
|
|
127
|
+
const { encode, rfc8949EncodeOptions } = await import('cborg');
|
|
128
|
+
const { sha3_256 } = await import('@noble/hashes/sha3.js');
|
|
129
|
+
// CBOR-encode with deterministic options, then sha3_256 — must match facilitator's computeDigest
|
|
130
|
+
const cborBytes = encode(auth, rfc8949EncodeOptions);
|
|
131
|
+
const digest = sha3_256(cborBytes);
|
|
132
|
+
const signature = await identity.sign(digest);
|
|
133
|
+
// Build the ic-auth envelope: { p: publicKey, s: signature }
|
|
134
|
+
const publicKeyDer = identity.getPublicKey().toDer();
|
|
135
|
+
const envelope = encode({
|
|
136
|
+
p: new Uint8Array(publicKeyDer),
|
|
137
|
+
s: new Uint8Array(signature),
|
|
138
|
+
});
|
|
139
|
+
// Return as base64url
|
|
140
|
+
return base64urlEncode(new Uint8Array(envelope));
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Register the ICP exact payment scheme with an x402Client.
|
|
146
|
+
*
|
|
147
|
+
* Mirrors registerExactEvmScheme(client, { signer }) from @x402/evm.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* import { x402Client } from '@x402/core/client'
|
|
152
|
+
* import { wrapFetchWithPayment } from '@x402/fetch'
|
|
153
|
+
* import { registerExactIcpScheme, createPemSigner } from '@canister-software/x402-icp/client'
|
|
154
|
+
* import fs from 'fs'
|
|
155
|
+
*
|
|
156
|
+
* const signer = await createPemSigner({ pem: fs.readFileSync('identity.pem', 'utf8') })
|
|
157
|
+
* const client = new x402Client()
|
|
158
|
+
* registerExactIcpScheme(client, { signer })
|
|
159
|
+
* const fetchWithPayment = wrapFetchWithPayment(fetch, client)
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export function registerExactIcpScheme(client, opts) {
|
|
163
|
+
return client.register('icp:*', new ExactIcpClient(opts.signer));
|
|
164
|
+
}
|
|
165
|
+
// ─── Convenience: pemToSigner ────────────────────────────────────────────────
|
|
166
|
+
/**
|
|
167
|
+
* Convenience function to create a signer from a PEM file path.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* import { pemToSigner } from '@canister-software/x402-icp/client'
|
|
172
|
+
*
|
|
173
|
+
* const signer = await pemToSigner('~/.config/dfx/identity/default/identity.pem')
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
export async function pemToSigner(pemPath, host) {
|
|
177
|
+
const fs = await import('fs');
|
|
178
|
+
const pem = fs.readFileSync(pemPath, 'utf8');
|
|
179
|
+
return createPemSigner({ pem, host });
|
|
180
|
+
}
|
|
181
|
+
// ─── Utilities ───────────────────────────────────────────────────────────────
|
|
182
|
+
function base64urlEncode(bytes) {
|
|
183
|
+
const b64 = Buffer.from(bytes).toString('base64');
|
|
184
|
+
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=signer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.js","sourceRoot":"","sources":["../../src/client/signer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,4BAA4B,EAA4C,MAAM,mBAAmB,CAAA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PaymentRequirements } from '../types/index.js';
|
|
2
|
+
export interface CreateIcpPaymentRequirementsOptions {
|
|
3
|
+
/** Amount in smallest unit (e8s for ICP, e6s for ckUSDC/ckUSDT) */
|
|
4
|
+
amount: string;
|
|
5
|
+
/** Network identifier, e.g. 'icp:1:ryjl3-tyaaa-aaaaa-aaaba-cai' */
|
|
6
|
+
network: string;
|
|
7
|
+
/** Recipient principal ID */
|
|
8
|
+
payTo: string;
|
|
9
|
+
/** Max timeout in seconds (default: 300) */
|
|
10
|
+
maxTimeoutSeconds?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Extra fields to include in payment requirements.
|
|
13
|
+
*
|
|
14
|
+
* When using a facilitator, `facilitatorPrincipal` is injected automatically
|
|
15
|
+
* by `ExactIcpScheme.enhancePaymentRequirements` — you do not need to set it here.
|
|
16
|
+
*
|
|
17
|
+
* For self-hosted settlement (no facilitator), pass it manually:
|
|
18
|
+
* `extra: { facilitatorPrincipal: 'your-principal' }`
|
|
19
|
+
*/
|
|
20
|
+
extra?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
export declare function createIcpPaymentRequirements(opts: CreateIcpPaymentRequirementsOptions): PaymentRequirements;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ASSETS } from '../types/index.js';
|
|
2
|
+
export function createIcpPaymentRequirements(opts) {
|
|
3
|
+
// network format: icp:1:<canisterId>
|
|
4
|
+
const ledgerId = opts.network.split(':').pop();
|
|
5
|
+
const asset = ASSETS[ledgerId];
|
|
6
|
+
return {
|
|
7
|
+
scheme: 'exact',
|
|
8
|
+
network: opts.network,
|
|
9
|
+
amount: opts.amount,
|
|
10
|
+
asset: opts.network, // asset = full network string for ICP
|
|
11
|
+
payTo: opts.payTo,
|
|
12
|
+
maxTimeoutSeconds: opts.maxTimeoutSeconds ?? 300,
|
|
13
|
+
extra: {
|
|
14
|
+
...(asset ? { symbol: asset.symbol, decimals: asset.decimals } : {}),
|
|
15
|
+
...opts.extra,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=requirements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirements.js","sourceRoot":"","sources":["../../src/server/requirements.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAuB1C,MAAM,UAAU,4BAA4B,CAC1C,IAAyC;IAEzC,qCAAqC;IACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAA;IAC/C,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;IAE9B,OAAO;QACL,MAAM,EAAE,OAAO;QACf,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,sCAAsC;QAC3D,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,iBAAiB,EAAE,IAAI,CAAC,iBAAiB,IAAI,GAAG;QAChD,KAAK,EAAE;YACL,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpE,GAAG,IAAI,CAAC,KAAK;SACd;KACqB,CAAA;AAC1B,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PaymentRequirements, Network, Price, AssetAmount, SchemeNetworkServer } from '@x402/core/types';
|
|
2
|
+
export declare class ExactIcpScheme implements SchemeNetworkServer {
|
|
3
|
+
readonly scheme = "exact";
|
|
4
|
+
/**
|
|
5
|
+
* Convert a price to ICP asset amount.
|
|
6
|
+
*
|
|
7
|
+
* - If price is already an AssetAmount, pass through.
|
|
8
|
+
* - If price is a string/number in smallest units (e8s), use with the
|
|
9
|
+
* network's default asset.
|
|
10
|
+
* - USD-denominated prices (e.g. "$0.10") are not yet supported.
|
|
11
|
+
*/
|
|
12
|
+
parsePrice(price: Price, network: Network): Promise<AssetAmount>;
|
|
13
|
+
/**
|
|
14
|
+
* Merge facilitator extra data (e.g. facilitatorPrincipal) into requirements.
|
|
15
|
+
*/
|
|
16
|
+
enhancePaymentRequirements(paymentRequirements: PaymentRequirements, supportedKind: {
|
|
17
|
+
x402Version: number;
|
|
18
|
+
scheme: string;
|
|
19
|
+
network: Network;
|
|
20
|
+
extra?: Record<string, unknown>;
|
|
21
|
+
}, _facilitatorExtensions: string[]): Promise<PaymentRequirements>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ASSETS } from '../types/index.js';
|
|
2
|
+
export class ExactIcpScheme {
|
|
3
|
+
scheme = 'exact';
|
|
4
|
+
/**
|
|
5
|
+
* Convert a price to ICP asset amount.
|
|
6
|
+
*
|
|
7
|
+
* - If price is already an AssetAmount, pass through.
|
|
8
|
+
* - If price is a string/number in smallest units (e8s), use with the
|
|
9
|
+
* network's default asset.
|
|
10
|
+
* - USD-denominated prices (e.g. "$0.10") are not yet supported.
|
|
11
|
+
*/
|
|
12
|
+
async parsePrice(price, network) {
|
|
13
|
+
// Already an AssetAmount
|
|
14
|
+
if (typeof price === 'object' && 'amount' in price && 'asset' in price) {
|
|
15
|
+
return price;
|
|
16
|
+
}
|
|
17
|
+
const raw = typeof price === 'number' ? price.toString() : price;
|
|
18
|
+
if (raw.startsWith('$')) {
|
|
19
|
+
throw new Error('USD price conversion not yet supported for ICP. ' +
|
|
20
|
+
'Specify amount in smallest token units (e8s for ICP, e6s for ckUSDC).');
|
|
21
|
+
}
|
|
22
|
+
// network format: icp:1:<canisterId>
|
|
23
|
+
const ledgerId = network.split(':').pop();
|
|
24
|
+
const asset = ASSETS[ledgerId];
|
|
25
|
+
if (!asset) {
|
|
26
|
+
throw new Error(`Unknown ICP asset for network: ${network}`);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
amount: raw,
|
|
30
|
+
asset: network, // asset = full network string, facilitator strips canisterId
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Merge facilitator extra data (e.g. facilitatorPrincipal) into requirements.
|
|
35
|
+
*/
|
|
36
|
+
async enhancePaymentRequirements(paymentRequirements, supportedKind, _facilitatorExtensions) {
|
|
37
|
+
const ledgerId = paymentRequirements.network.split(':').pop();
|
|
38
|
+
const asset = ASSETS[ledgerId];
|
|
39
|
+
return {
|
|
40
|
+
...paymentRequirements,
|
|
41
|
+
extra: {
|
|
42
|
+
...paymentRequirements.extra,
|
|
43
|
+
// Merge in facilitator-provided extra (e.g. facilitatorPrincipal)
|
|
44
|
+
...supportedKind.extra,
|
|
45
|
+
// Add asset metadata for clients
|
|
46
|
+
...(asset ? { symbol: asset.symbol, decimals: asset.decimals } : {}),
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=scheme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheme.js","sourceRoot":"","sources":["../../src/server/scheme.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE1C,MAAM,OAAO,cAAc;IAChB,MAAM,GAAG,OAAO,CAAA;IAEzB;;;;;;;OAOG;IACH,KAAK,CAAC,UAAU,CAAC,KAAY,EAAE,OAAgB;QAC7C,yBAAyB;QACzB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,QAAQ,IAAI,KAAK,IAAI,OAAO,IAAI,KAAK,EAAE,CAAC;YACvE,OAAO,KAAK,CAAA;QACd,CAAC;QAED,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAe,CAAA;QAE1E,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,kDAAkD;gBAClD,uEAAuE,CACxE,CAAA;QACH,CAAC;QAED,qCAAqC;QACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAA;QAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;QAE9B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,kCAAkC,OAAO,EAAE,CAAC,CAAA;QAC9D,CAAC;QAED,OAAO;YACL,MAAM,EAAE,GAAG;YACX,KAAK,EAAE,OAAO,EAAE,6DAA6D;SAC9E,CAAA;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,0BAA0B,CAC9B,mBAAwC,EACxC,aAKC,EACD,sBAAgC;QAEhC,MAAM,QAAQ,GAAG,mBAAmB,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAA;QAC9D,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;QAE9B,OAAO;YACL,GAAG,mBAAmB;YACtB,KAAK,EAAE;gBACL,GAAG,mBAAmB,CAAC,KAAK;gBAC5B,kEAAkE;gBAClE,GAAG,aAAa,CAAC,KAAK;gBACtB,iCAAiC;gBACjC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACrE;SACF,CAAA;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type { PaymentPayload, PaymentRequirements, VerifyResponse, SettleResponse, Network, Price, AssetAmount, SchemeNetworkServer, } from '@x402/core/types';
|
|
2
|
+
export interface IcpPayloadAuthorization {
|
|
3
|
+
to: string;
|
|
4
|
+
value: string;
|
|
5
|
+
expiresAt: number;
|
|
6
|
+
nonce: number;
|
|
7
|
+
}
|
|
8
|
+
export interface IcpPayload {
|
|
9
|
+
signature: string;
|
|
10
|
+
authorization: IcpPayloadAuthorization;
|
|
11
|
+
}
|
|
12
|
+
export interface AssetConfig {
|
|
13
|
+
symbol: string;
|
|
14
|
+
name: string;
|
|
15
|
+
decimals: number;
|
|
16
|
+
ledgerId: string;
|
|
17
|
+
transferFee: number;
|
|
18
|
+
}
|
|
19
|
+
export declare const ASSETS: Record<string, AssetConfig>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const ASSETS = {
|
|
2
|
+
'ryjl3-tyaaa-aaaaa-aaaba-cai': {
|
|
3
|
+
symbol: 'ICP', name: 'Internet Computer',
|
|
4
|
+
decimals: 8, ledgerId: 'ryjl3-tyaaa-aaaaa-aaaba-cai', transferFee: 10_000,
|
|
5
|
+
},
|
|
6
|
+
'xevnm-gaaaa-aaaar-qafnq-cai': {
|
|
7
|
+
symbol: 'ckUSDC', name: 'Chain-key USDC',
|
|
8
|
+
decimals: 6, ledgerId: 'xevnm-gaaaa-aaaar-qafnq-cai', transferFee: 10_000,
|
|
9
|
+
},
|
|
10
|
+
'cngnf-vqaaa-aaaar-qag4q-cai': {
|
|
11
|
+
symbol: 'ckUSDT', name: 'Chain-key USDT',
|
|
12
|
+
decimals: 6, ledgerId: 'cngnf-vqaaa-aaaar-qag4q-cai', transferFee: 10_000,
|
|
13
|
+
},
|
|
14
|
+
// Testnet tokens (faucet.internetcomputer.org)
|
|
15
|
+
'xafvr-biaaa-aaaai-aql5q-cai': {
|
|
16
|
+
symbol: 'TESTICP', name: 'Test ICP',
|
|
17
|
+
decimals: 8, ledgerId: 'xafvr-biaaa-aaaai-aql5q-cai', transferFee: 10_000,
|
|
18
|
+
},
|
|
19
|
+
'3jkp5-oyaaa-aaaaj-azwqa-cai': {
|
|
20
|
+
symbol: 'TICRC1', name: 'Test ICRC-1',
|
|
21
|
+
decimals: 8, ledgerId: '3jkp5-oyaaa-aaaaj-azwqa-cai', transferFee: 10_000,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAmCA,MAAM,CAAC,MAAM,MAAM,GAAgC;IACjD,6BAA6B,EAAE;QAC7B,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,mBAAmB;QACxC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,6BAA6B,EAAE,WAAW,EAAE,MAAM;KAC1E;IACD,6BAA6B,EAAE;QAC7B,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB;QACxC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,6BAA6B,EAAE,WAAW,EAAE,MAAM;KAC1E;IACD,6BAA6B,EAAE;QAC7B,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB;QACxC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,6BAA6B,EAAE,WAAW,EAAE,MAAM;KAC1E;IACD,+CAA+C;IAC/C,6BAA6B,EAAE;QAC7B,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU;QACnC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,6BAA6B,EAAE,WAAW,EAAE,MAAM;KAC1E;IACD,6BAA6B,EAAE;QAC7B,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa;QACrC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,6BAA6B,EAAE,WAAW,EAAE,MAAM;KAC1E;CACF,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@canister-software/x402-icp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "x402 payment scheme for Internet Computer (ICP) — ICRC-2 approve/transferFrom",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./client": {
|
|
8
|
+
"types": "./dist/client/index.d.ts",
|
|
9
|
+
"import": "./dist/client/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./server": {
|
|
12
|
+
"types": "./dist/server/index.d.ts",
|
|
13
|
+
"import": "./dist/server/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./types": {
|
|
16
|
+
"types": "./dist/types/index.d.ts",
|
|
17
|
+
"import": "./dist/types/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"lint": "eslint src",
|
|
24
|
+
"format": "prettier --write src",
|
|
25
|
+
"format:check": "prettier --check src"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"x402",
|
|
29
|
+
"icp",
|
|
30
|
+
"internet-computer",
|
|
31
|
+
"icrc2",
|
|
32
|
+
"payments",
|
|
33
|
+
"web3",
|
|
34
|
+
"http-payments",
|
|
35
|
+
"dfinity",
|
|
36
|
+
"canister"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@x402/core": ">=2.4.0"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@dfinity/agent": "^2.2.0",
|
|
46
|
+
"@dfinity/candid": "^2.2.0",
|
|
47
|
+
"@dfinity/identity": "^2.2.0",
|
|
48
|
+
"@dfinity/identity-secp256k1": "^2.4.1",
|
|
49
|
+
"@dfinity/principal": "^2.2.0",
|
|
50
|
+
"@noble/hashes": "^1.8.0",
|
|
51
|
+
"cborg": "^4.2.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^22.0.0",
|
|
55
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
56
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
57
|
+
"eslint": "^9.0.0",
|
|
58
|
+
"eslint-config-prettier": "^10.0.0",
|
|
59
|
+
"prettier": "^3.0.0",
|
|
60
|
+
"typescript": "^5.7.0"
|
|
61
|
+
},
|
|
62
|
+
"files": [
|
|
63
|
+
"dist",
|
|
64
|
+
"README.md",
|
|
65
|
+
"LICENSE"
|
|
66
|
+
],
|
|
67
|
+
"author": "Canister Software Inc.",
|
|
68
|
+
"license": "MIT",
|
|
69
|
+
"homepage": "https://github.com/Canister-Software/x402-icp#readme",
|
|
70
|
+
"bugs": {
|
|
71
|
+
"url": "https://github.com/Canister-Software/x402-icp/issues"
|
|
72
|
+
},
|
|
73
|
+
"repository": {
|
|
74
|
+
"type": "git",
|
|
75
|
+
"url": "https://github.com/Canister-Software/x402-icp"
|
|
76
|
+
}
|
|
77
|
+
}
|