@dexterai/x402 3.8.1 → 3.9.1
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 +313 -702
- package/dist/adapters/index.cjs +1 -1
- package/dist/adapters/index.d.cts +3 -3
- package/dist/adapters/index.d.ts +3 -3
- package/dist/adapters/index.js +1 -1
- package/dist/batch-settlement/index.d.cts +3 -3
- package/dist/batch-settlement/index.d.ts +3 -3
- package/dist/batch-settlement/seller/index.d.cts +4 -4
- package/dist/batch-settlement/seller/index.d.ts +4 -4
- package/dist/client/index.cjs +1 -1
- package/dist/client/index.d.cts +308 -238
- package/dist/client/index.d.ts +308 -238
- package/dist/client/index.js +1 -1
- package/dist/{constants-qU-4U3L-.d.cts → constants-D41hDAG6.d.cts} +13 -1
- package/dist/{constants-qU-4U3L-.d.ts → constants-D41hDAG6.d.ts} +13 -1
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.d.cts +12 -4
- package/dist/react/index.d.ts +12 -4
- package/dist/react/index.js +1 -1
- package/dist/server/index.cjs +3 -3
- package/dist/server/index.d.cts +63 -5
- package/dist/server/index.d.ts +63 -5
- package/dist/server/index.js +3 -3
- package/dist/tab/adapters/solana/index.cjs +1 -0
- package/dist/tab/adapters/solana/index.d.cts +123 -0
- package/dist/tab/adapters/solana/index.d.ts +123 -0
- package/dist/tab/adapters/solana/index.js +1 -0
- package/dist/tab/index.cjs +6 -0
- package/dist/tab/index.d.cts +29 -0
- package/dist/tab/index.d.ts +29 -0
- package/dist/tab/index.js +6 -0
- package/dist/tab/seller/index.cjs +6 -0
- package/dist/tab/seller/index.d.cts +291 -0
- package/dist/tab/seller/index.d.ts +291 -0
- package/dist/tab/seller/index.js +6 -0
- package/dist/{types-XG8QvfyL.d.cts → types-C8lyIOmX.d.cts} +1 -1
- package/dist/{types-DllrEG_Z.d.ts → types-CTl7yVq6.d.ts} +1 -1
- package/dist/{types-pOwQlGEV.d.cts → types-CiPcPs0w.d.cts} +1 -1
- package/dist/{types-DDBPREEu.d.ts → types-D9VMq7In.d.ts} +1 -1
- package/dist/types-DIrmhiD-.d.cts +234 -0
- package/dist/types-DIrmhiD-.d.ts +234 -0
- package/dist/{types-htvWHuW3.d.cts → types-RxdlGPaG.d.cts} +122 -1
- package/dist/{types-htvWHuW3.d.ts → types-RxdlGPaG.d.ts} +122 -1
- package/dist/utils/index.cjs +1 -1
- package/dist/utils/index.d.cts +10 -1
- package/dist/utils/index.d.ts +10 -1
- package/dist/utils/index.js +1 -1
- package/dist/{sponsored-access-D96FgkQK.d.ts → x402-client-CHrU2Bs6.d.cts} +114 -63
- package/dist/{sponsored-access-D7H-womP.d.cts → x402-client-CzseAnIt.d.ts} +114 -63
- package/package.json +18 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { PublicKey, Connection, Signer, ConfirmOptions } from '@solana/web3.js';
|
|
2
|
+
import { f as VaultAdapter } from '../../../types-DIrmhiD-.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Session-key lifecycle: generation, voucher signing, in-memory hygiene.
|
|
6
|
+
*
|
|
7
|
+
* A session key is an ed25519 keypair generated in process memory at tab
|
|
8
|
+
* open. The buyer's passkey signs a 180-byte registration message endorsing
|
|
9
|
+
* the session pubkey within a scope (counterparty, max amount, expiry).
|
|
10
|
+
* From that point until tab close, the session key signs every voucher; the
|
|
11
|
+
* passkey is never invoked again for the lifetime of the tab.
|
|
12
|
+
*
|
|
13
|
+
* The session keypair is NEVER persisted to disk. A crashed process
|
|
14
|
+
* forfeits the session; the buyer re-prompts the passkey on the next
|
|
15
|
+
* attempt. This is the right default — a session key on disk is a real
|
|
16
|
+
* attack surface, the cost of re-authorizing is a single prompt.
|
|
17
|
+
*
|
|
18
|
+
* Curve choice: ed25519. It's Solana's native signer, every Solana RPC and
|
|
19
|
+
* wallet knows how to verify it, and `tweetnacl` (already a SDK dep) gives
|
|
20
|
+
* us a deterministic implementation that doesn't pull in heavy crypto.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
declare function deriveChannelId(args: {
|
|
24
|
+
vaultPda: PublicKey;
|
|
25
|
+
sellerUrl: string;
|
|
26
|
+
nonce: bigint;
|
|
27
|
+
}): Uint8Array;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* CLI / Node passkey signer using @noble/curves/p256.
|
|
31
|
+
*
|
|
32
|
+
* In the browser, the vault's root authority is a WebAuthn credential —
|
|
33
|
+
* the user taps a Touch ID prompt and the browser hands back a
|
|
34
|
+
* clientDataJSON / authenticatorData / signature triple. In CLI and Node
|
|
35
|
+
* environments we have no platform passkey, so we use noble-curves to
|
|
36
|
+
* sign with a locally-stored P-256 keypair, then synthesize the same
|
|
37
|
+
* clientDataJSON / authenticatorData shape the on-chain verifier expects.
|
|
38
|
+
*
|
|
39
|
+
* From the on-chain program's perspective the two paths are
|
|
40
|
+
* indistinguishable: both produce a 64-byte (r||s) low-S secp256r1
|
|
41
|
+
* signature over `authenticatorData || sha256(clientDataJSON)`, where
|
|
42
|
+
* `clientDataJSON.challenge` base64url-decodes to sha256(operation_msg).
|
|
43
|
+
*
|
|
44
|
+
* This module mirrors the helper at
|
|
45
|
+
* dexter-vault/tests/helpers/secp256r1.ts (signOperationWithPasskey).
|
|
46
|
+
* Keep them in lockstep.
|
|
47
|
+
*/
|
|
48
|
+
interface P256Keypair {
|
|
49
|
+
/** 33-byte SEC1 compressed public key (the form the vault stores). */
|
|
50
|
+
publicKey: Uint8Array;
|
|
51
|
+
/** 32-byte raw scalar. NEVER persist this anywhere user-readable. */
|
|
52
|
+
privateKey: Uint8Array;
|
|
53
|
+
}
|
|
54
|
+
interface SignedPasskeyPayload {
|
|
55
|
+
/** Pass straight into the vault instruction's `client_data_json` arg. */
|
|
56
|
+
clientDataJSON: Uint8Array;
|
|
57
|
+
/** Pass straight into the vault instruction's `authenticator_data` arg. */
|
|
58
|
+
authenticatorData: Uint8Array;
|
|
59
|
+
/** Pass to buildSecp256r1VerifyInstruction as `message`. */
|
|
60
|
+
precompileMessage: Uint8Array;
|
|
61
|
+
/** Pass to buildSecp256r1VerifyInstruction as `signature`. */
|
|
62
|
+
signature: Uint8Array;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Solana VaultAdapter — production implementation against the deployed
|
|
67
|
+
* dexter-vault v2 program on Solana mainnet.
|
|
68
|
+
*
|
|
69
|
+
* Two passkey signing paths are supported via the `passkeySigner` field:
|
|
70
|
+
* - CLI/Node: a noble-curves P-256 signer wrapping a local keypair
|
|
71
|
+
* (`./passkey-noble.ts`).
|
|
72
|
+
* - Browser: a WebAuthn-backed signer (lands in Phase 5 / React work).
|
|
73
|
+
*
|
|
74
|
+
* The adapter's job is to (a) take the buyer's session scope, (b) get a
|
|
75
|
+
* passkey signature endorsing it, (c) submit the on-chain
|
|
76
|
+
* register_session_key tx so the seller can verify the endorsement, (d)
|
|
77
|
+
* expose voucher signing for the session, and (e) tear the session down
|
|
78
|
+
* at close.
|
|
79
|
+
*
|
|
80
|
+
* The adapter does NOT touch pending_voucher_count. That counter belongs
|
|
81
|
+
* to the facilitator's dexter_authority and is moved via settle_voucher
|
|
82
|
+
* during seller settlement. The SDK's `Tab.close()` will hand a cumulative
|
|
83
|
+
* voucher to the facilitator in Phase 3; Phase 2 stops at the session
|
|
84
|
+
* register/revoke layer.
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
interface PasskeySigner {
|
|
88
|
+
/** 33-byte SEC1 compressed P-256 public key. The vault stores this on
|
|
89
|
+
* init; the on-chain verifier compares against it on every passkey-
|
|
90
|
+
* signed instruction. */
|
|
91
|
+
publicKey: Uint8Array;
|
|
92
|
+
/** Sign an arbitrary operation-message bundle in the WebAuthn shape the
|
|
93
|
+
* on-chain verifier expects. The CLI path uses noble-curves; the
|
|
94
|
+
* browser path will use navigator.credentials.get(). */
|
|
95
|
+
signOperation(operationMessage: Uint8Array): Promise<SignedPasskeyPayload>;
|
|
96
|
+
}
|
|
97
|
+
/** Build a PasskeySigner from a locally-held P-256 keypair (CLI path). */
|
|
98
|
+
declare function passkeySignerFromP256Keypair(kp: P256Keypair): PasskeySigner;
|
|
99
|
+
interface CreateSolanaVaultAdapterOptions {
|
|
100
|
+
/** RPC the adapter uses to submit txs. The buyer can pass their own
|
|
101
|
+
* connection (browser wallet RPC, Helius URL, etc.) — the adapter has
|
|
102
|
+
* no opinion. */
|
|
103
|
+
connection: Connection;
|
|
104
|
+
/** The buyer's Swig wallet (holds USDC). */
|
|
105
|
+
swigAddress: string | PublicKey;
|
|
106
|
+
/** The buyer's vault PDA (gate account). */
|
|
107
|
+
vaultPda: string | PublicKey;
|
|
108
|
+
/** The passkey signing path. */
|
|
109
|
+
passkeySigner: PasskeySigner;
|
|
110
|
+
/** Lamport-fee payer. In Phase 2 this is the buyer; later phases may
|
|
111
|
+
* route through a facilitator co-signer. Required because the buyer's
|
|
112
|
+
* vault account is not a signer for register/revoke (the passkey
|
|
113
|
+
* signature in the precompile sibling is the authorization). */
|
|
114
|
+
feePayer: Signer;
|
|
115
|
+
/** Confirmation options for sendAndConfirm. Defaults to 'confirmed' to
|
|
116
|
+
* match production code (FE/API). For test suites, override to
|
|
117
|
+
* 'finalized' — see reference_anchor_test_commitment in repo memory. */
|
|
118
|
+
confirmOptions?: ConfirmOptions;
|
|
119
|
+
}
|
|
120
|
+
/** Factory entry point. */
|
|
121
|
+
declare function createSolanaVaultAdapter(opts: CreateSolanaVaultAdapterOptions): VaultAdapter;
|
|
122
|
+
|
|
123
|
+
export { type CreateSolanaVaultAdapterOptions, type PasskeySigner, createSolanaVaultAdapter, deriveChannelId, passkeySignerFromP256Keypair };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { PublicKey, Connection, Signer, ConfirmOptions } from '@solana/web3.js';
|
|
2
|
+
import { f as VaultAdapter } from '../../../types-DIrmhiD-.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Session-key lifecycle: generation, voucher signing, in-memory hygiene.
|
|
6
|
+
*
|
|
7
|
+
* A session key is an ed25519 keypair generated in process memory at tab
|
|
8
|
+
* open. The buyer's passkey signs a 180-byte registration message endorsing
|
|
9
|
+
* the session pubkey within a scope (counterparty, max amount, expiry).
|
|
10
|
+
* From that point until tab close, the session key signs every voucher; the
|
|
11
|
+
* passkey is never invoked again for the lifetime of the tab.
|
|
12
|
+
*
|
|
13
|
+
* The session keypair is NEVER persisted to disk. A crashed process
|
|
14
|
+
* forfeits the session; the buyer re-prompts the passkey on the next
|
|
15
|
+
* attempt. This is the right default — a session key on disk is a real
|
|
16
|
+
* attack surface, the cost of re-authorizing is a single prompt.
|
|
17
|
+
*
|
|
18
|
+
* Curve choice: ed25519. It's Solana's native signer, every Solana RPC and
|
|
19
|
+
* wallet knows how to verify it, and `tweetnacl` (already a SDK dep) gives
|
|
20
|
+
* us a deterministic implementation that doesn't pull in heavy crypto.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
declare function deriveChannelId(args: {
|
|
24
|
+
vaultPda: PublicKey;
|
|
25
|
+
sellerUrl: string;
|
|
26
|
+
nonce: bigint;
|
|
27
|
+
}): Uint8Array;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* CLI / Node passkey signer using @noble/curves/p256.
|
|
31
|
+
*
|
|
32
|
+
* In the browser, the vault's root authority is a WebAuthn credential —
|
|
33
|
+
* the user taps a Touch ID prompt and the browser hands back a
|
|
34
|
+
* clientDataJSON / authenticatorData / signature triple. In CLI and Node
|
|
35
|
+
* environments we have no platform passkey, so we use noble-curves to
|
|
36
|
+
* sign with a locally-stored P-256 keypair, then synthesize the same
|
|
37
|
+
* clientDataJSON / authenticatorData shape the on-chain verifier expects.
|
|
38
|
+
*
|
|
39
|
+
* From the on-chain program's perspective the two paths are
|
|
40
|
+
* indistinguishable: both produce a 64-byte (r||s) low-S secp256r1
|
|
41
|
+
* signature over `authenticatorData || sha256(clientDataJSON)`, where
|
|
42
|
+
* `clientDataJSON.challenge` base64url-decodes to sha256(operation_msg).
|
|
43
|
+
*
|
|
44
|
+
* This module mirrors the helper at
|
|
45
|
+
* dexter-vault/tests/helpers/secp256r1.ts (signOperationWithPasskey).
|
|
46
|
+
* Keep them in lockstep.
|
|
47
|
+
*/
|
|
48
|
+
interface P256Keypair {
|
|
49
|
+
/** 33-byte SEC1 compressed public key (the form the vault stores). */
|
|
50
|
+
publicKey: Uint8Array;
|
|
51
|
+
/** 32-byte raw scalar. NEVER persist this anywhere user-readable. */
|
|
52
|
+
privateKey: Uint8Array;
|
|
53
|
+
}
|
|
54
|
+
interface SignedPasskeyPayload {
|
|
55
|
+
/** Pass straight into the vault instruction's `client_data_json` arg. */
|
|
56
|
+
clientDataJSON: Uint8Array;
|
|
57
|
+
/** Pass straight into the vault instruction's `authenticator_data` arg. */
|
|
58
|
+
authenticatorData: Uint8Array;
|
|
59
|
+
/** Pass to buildSecp256r1VerifyInstruction as `message`. */
|
|
60
|
+
precompileMessage: Uint8Array;
|
|
61
|
+
/** Pass to buildSecp256r1VerifyInstruction as `signature`. */
|
|
62
|
+
signature: Uint8Array;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Solana VaultAdapter — production implementation against the deployed
|
|
67
|
+
* dexter-vault v2 program on Solana mainnet.
|
|
68
|
+
*
|
|
69
|
+
* Two passkey signing paths are supported via the `passkeySigner` field:
|
|
70
|
+
* - CLI/Node: a noble-curves P-256 signer wrapping a local keypair
|
|
71
|
+
* (`./passkey-noble.ts`).
|
|
72
|
+
* - Browser: a WebAuthn-backed signer (lands in Phase 5 / React work).
|
|
73
|
+
*
|
|
74
|
+
* The adapter's job is to (a) take the buyer's session scope, (b) get a
|
|
75
|
+
* passkey signature endorsing it, (c) submit the on-chain
|
|
76
|
+
* register_session_key tx so the seller can verify the endorsement, (d)
|
|
77
|
+
* expose voucher signing for the session, and (e) tear the session down
|
|
78
|
+
* at close.
|
|
79
|
+
*
|
|
80
|
+
* The adapter does NOT touch pending_voucher_count. That counter belongs
|
|
81
|
+
* to the facilitator's dexter_authority and is moved via settle_voucher
|
|
82
|
+
* during seller settlement. The SDK's `Tab.close()` will hand a cumulative
|
|
83
|
+
* voucher to the facilitator in Phase 3; Phase 2 stops at the session
|
|
84
|
+
* register/revoke layer.
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
interface PasskeySigner {
|
|
88
|
+
/** 33-byte SEC1 compressed P-256 public key. The vault stores this on
|
|
89
|
+
* init; the on-chain verifier compares against it on every passkey-
|
|
90
|
+
* signed instruction. */
|
|
91
|
+
publicKey: Uint8Array;
|
|
92
|
+
/** Sign an arbitrary operation-message bundle in the WebAuthn shape the
|
|
93
|
+
* on-chain verifier expects. The CLI path uses noble-curves; the
|
|
94
|
+
* browser path will use navigator.credentials.get(). */
|
|
95
|
+
signOperation(operationMessage: Uint8Array): Promise<SignedPasskeyPayload>;
|
|
96
|
+
}
|
|
97
|
+
/** Build a PasskeySigner from a locally-held P-256 keypair (CLI path). */
|
|
98
|
+
declare function passkeySignerFromP256Keypair(kp: P256Keypair): PasskeySigner;
|
|
99
|
+
interface CreateSolanaVaultAdapterOptions {
|
|
100
|
+
/** RPC the adapter uses to submit txs. The buyer can pass their own
|
|
101
|
+
* connection (browser wallet RPC, Helius URL, etc.) — the adapter has
|
|
102
|
+
* no opinion. */
|
|
103
|
+
connection: Connection;
|
|
104
|
+
/** The buyer's Swig wallet (holds USDC). */
|
|
105
|
+
swigAddress: string | PublicKey;
|
|
106
|
+
/** The buyer's vault PDA (gate account). */
|
|
107
|
+
vaultPda: string | PublicKey;
|
|
108
|
+
/** The passkey signing path. */
|
|
109
|
+
passkeySigner: PasskeySigner;
|
|
110
|
+
/** Lamport-fee payer. In Phase 2 this is the buyer; later phases may
|
|
111
|
+
* route through a facilitator co-signer. Required because the buyer's
|
|
112
|
+
* vault account is not a signer for register/revoke (the passkey
|
|
113
|
+
* signature in the precompile sibling is the authorization). */
|
|
114
|
+
feePayer: Signer;
|
|
115
|
+
/** Confirmation options for sendAndConfirm. Defaults to 'confirmed' to
|
|
116
|
+
* match production code (FE/API). For test suites, override to
|
|
117
|
+
* 'finalized' — see reference_anchor_test_commitment in repo memory. */
|
|
118
|
+
confirmOptions?: ConfirmOptions;
|
|
119
|
+
}
|
|
120
|
+
/** Factory entry point. */
|
|
121
|
+
declare function createSolanaVaultAdapter(opts: CreateSolanaVaultAdapterOptions): VaultAdapter;
|
|
122
|
+
|
|
123
|
+
export { type CreateSolanaVaultAdapterOptions, type PasskeySigner, createSolanaVaultAdapter, deriveChannelId, passkeySignerFromP256Keypair };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{PublicKey as _,Transaction as C}from"@solana/web3.js";import{PublicKey as A,TransactionInstruction as b}from"@solana/web3.js";var f=new A("Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc"),M=new A("Secp256r1SigVerify1111111111111111111111111"),U=new A("Sysvar1nstructions1111111111111111111111111"),$=new Uint8Array([69,94,60,44,49,199,183,233]),J=new Uint8Array([81,192,32,110,104,116,144,151]);function L(e){let t=new Uint8Array(8);return new DataView(t.buffer).setBigUint64(0,e,!0),t}function z(e){let t=new Uint8Array(8);return new DataView(t.buffer).setBigInt64(0,e,!0),t}function F(e){let t=new Uint8Array(4);return new DataView(t.buffer).setUint32(0,e>>>0,!0),t}function m(e){let t=new Uint8Array(4+e.length);return new DataView(t.buffer).setUint32(0,e.length>>>0,!0),t.set(e,4),t}function K(...e){let t=e.reduce((s,r)=>s+r.length,0),n=new Uint8Array(t),i=0;for(let s of e)n.set(s,i),i+=s.length;return n}var d=64,h=33,G=14,y=2;function w(e,t,n){if(e.length!==h)throw new Error(`expected ${h}-byte compressed pubkey`);if(t.length!==d)throw new Error(`expected ${d}-byte signature`);let i=y+G,s=i+d,r=s+h,c=n.length,u=r+c,o=new Uint8Array(u);o[0]=1,o[1]=0;let a=new DataView(o.buffer);return a.setUint16(y+0,i,!0),a.setUint16(y+2,65535,!0),a.setUint16(y+4,s,!0),a.setUint16(y+6,65535,!0),a.setUint16(y+8,r,!0),a.setUint16(y+10,c,!0),a.setUint16(y+12,65535,!0),o.set(t,i),o.set(e,s),o.set(n,r),new b({keys:[],programId:M,data:Buffer.from(o)})}function x(e){if(e.sessionPubkey.length!==32)throw new Error(`sessionPubkey must be 32 bytes, got ${e.sessionPubkey.length}`);let t=K($,e.sessionPubkey,L(e.maxAmount),z(e.expiresAt),e.allowedCounterparty.toBytes(),F(e.nonce),m(e.clientDataJSON),m(e.authenticatorData));return new b({keys:[{pubkey:e.vaultPda,isSigner:!1,isWritable:!0},{pubkey:U,isSigner:!1,isWritable:!1}],programId:f,data:Buffer.from(t)})}function v(e){let t=K(J,m(e.clientDataJSON),m(e.authenticatorData));return new b({keys:[{pubkey:e.vaultPda,isSigner:!1,isWritable:!0},{pubkey:U,isSigner:!1,isWritable:!1}],programId:f,data:Buffer.from(t)})}var W=(()=>{let e=new Uint8Array(32);return e.set(new TextEncoder().encode("OTS_SESSION_REGISTER_V1"),0),e})(),Y=(()=>{let e=new Uint8Array(32);return e.set(new TextEncoder().encode("OTS_SESSION_REVOKE_V1"),0),e})();function k(e){if(e.sessionPubkey.length!==32)throw new Error(`sessionPubkey must be 32 bytes, got ${e.sessionPubkey.length}`);let t=new Uint8Array(180),n=new DataView(t.buffer),i=0;if(t.set(W,i),i+=32,t.set(e.programId.toBytes(),i),i+=32,t.set(e.vaultPda.toBytes(),i),i+=32,t.set(e.sessionPubkey,i),i+=32,n.setBigUint64(i,e.maxAmount,!0),i+=8,n.setBigInt64(i,e.expiresAt,!0),i+=8,t.set(e.allowedCounterparty.toBytes(),i),i+=32,n.setUint32(i,e.nonce>>>0,!0),i+=4,i!==180)throw new Error(`internal: session register message wrong length ${i}, expected 180`);return t}function I(e){if(e.sessionPubkey.length!==32)throw new Error(`sessionPubkey must be 32 bytes, got ${e.sessionPubkey.length}`);let t=new Uint8Array(128),n=0;if(t.set(Y,n),n+=32,t.set(e.programId.toBytes(),n),n+=32,t.set(e.vaultPda.toBytes(),n),n+=32,t.set(e.sessionPubkey,n),n+=32,n!==128)throw new Error(`internal: session revoke message wrong length ${n}, expected 128`);return t}function E(e){if(e.channelId.length!==32)throw new Error(`channelId must be 32 bytes, got ${e.channelId.length}`);let t=new Uint8Array(44),n=new DataView(t.buffer),i=0;if(t.set(e.channelId,i),i+=32,n.setBigUint64(i,e.cumulativeAmount,!0),i+=8,n.setUint32(i,e.sequenceNumber>>>0,!0),i+=4,i!==44)throw new Error(`internal: voucher payload wrong length ${i}, expected 44`);return t}import O from"tweetnacl";import{sha256 as H}from"@noble/hashes/sha256";function D(){let e=O.sign.keyPair();return{publicKey:e.publicKey,privateKey:e.secretKey}}function R(e,t,n){return{publicKey:e.publicKey,privateKey:e.privateKey,scope:t,registration:n}}function B(e,t,n){if(n.length!==32)throw new Error(`channelIdBytes must be 32 bytes, got ${n.length}`);let i=BigInt(t.cumulativeAmount),s=BigInt(e.scope.maxAmountAtomic);if(i>s)throw new Error(`voucher cumulative ${i} exceeds session cap ${s}`);let r=Math.floor(Date.now()/1e3);if(r>=e.scope.expiresAtUnix)throw new Error(`session expired at ${e.scope.expiresAtUnix}, now ${r}`);let c=E({channelId:n,cumulativeAmount:i,sequenceNumber:t.sequenceNumber}),u=O.sign.detached(c,e.privateKey);return{payload:t,sessionPublicKey:e.publicKey,sessionRegistration:e.registration,sessionSignature:u}}function S(e){if(!/^\d+$/.test(e))throw new Error(`atomic amount must be a non-negative integer string, got "${e}"`);return BigInt(e)}function Z(e){let t=new Uint8Array(8);new DataView(t.buffer).setBigUint64(0,e.nonce,!0);let n=new TextEncoder().encode(e.sellerUrl),i=H.create();return i.update(e.vaultPda.toBytes()),i.update(n),i.update(t),i.digest()}import{p256 as q}from"@noble/curves/p256";import{sha256 as g}from"@noble/hashes/sha256";var V="dexter.cash";function j(e){return Buffer.from(e).toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function X(e,t=`https://${V}`){let i={type:"webauthn.get",challenge:j(e),origin:t,crossOrigin:!1};return new TextEncoder().encode(JSON.stringify(i))}function Q(e=1){let t=g(new TextEncoder().encode(V)),n=new Uint8Array(37);return n.set(t,0),n[32]=5,new DataView(n.buffer).setUint32(33,e,!1),n}function T(e,t){let n=g(t),i=X(n),s=Q(1),r=new Uint8Array(s.length+32);r.set(s,0),r.set(g(i),s.length);let c=g(r),o=q.sign(c,e.privateKey,{lowS:!0}).toCompactRawBytes();return{clientDataJSON:i,authenticatorData:s,precompileMessage:r,signature:o}}function we(e){return{publicKey:e.publicKey,signOperation:async t=>T(e,t)}}var P=class{network="solana:mainnet";swigAddress;vaultPda;connection;vaultPdaKey;passkey;feePayer;confirmOptions;constructor(t){this.connection=t.connection,this.swigAddress=typeof t.swigAddress=="string"?t.swigAddress:t.swigAddress.toBase58(),this.vaultPdaKey=typeof t.vaultPda=="string"?new _(t.vaultPda):t.vaultPda,this.vaultPda=this.vaultPdaKey.toBase58(),this.passkey=t.passkeySigner,this.feePayer=t.feePayer,this.confirmOptions=t.confirmOptions??{commitment:"confirmed"}}async authorizeSession(t){let n=new _(t.allowedCounterparty),i=D(),s=ee(),r=k({programId:f,vaultPda:this.vaultPdaKey,sessionPubkey:i.publicKey,maxAmount:S(t.maxAmountAtomic),expiresAt:BigInt(t.expiresAtUnix),allowedCounterparty:n,nonce:s}),c=await this.passkey.signOperation(r),u=w(this.passkey.publicKey,c.signature,c.precompileMessage),o=x({vaultPda:this.vaultPdaKey,sessionPubkey:i.publicKey,maxAmount:S(t.maxAmountAtomic),expiresAt:BigInt(t.expiresAtUnix),allowedCounterparty:n,nonce:s,clientDataJSON:c.clientDataJSON,authenticatorData:c.authenticatorData}),a=new C().add(u,o);a.feePayer=this.feePayer.publicKey;let{blockhash:l}=await this.connection.getLatestBlockhash(this.confirmOptions.commitment);a.recentBlockhash=l,a.sign(this.feePayer);let p=await this.connection.sendRawTransaction(a.serialize(),{skipPreflight:!1,preflightCommitment:this.confirmOptions.preflightCommitment??this.confirmOptions.commitment});return await this.connection.confirmTransaction({signature:p,blockhash:l,lastValidBlockHeight:(await this.connection.getLatestBlockhash(this.confirmOptions.commitment)).lastValidBlockHeight},this.confirmOptions.commitment),await this.waitForActiveSessionFinalized(i.publicKey),R(i,t,r)}async signWithSession(t,n){let i=await te(n.channelId);return B(t,n,i)}async signOpenTab(t,n){return t.registration}async signCloseTab(t,n,i){let s=I({programId:f,vaultPda:this.vaultPdaKey,sessionPubkey:t.publicKey}),r=await this.passkey.signOperation(s),c=w(this.passkey.publicKey,r.signature,r.precompileMessage),u=v({vaultPda:this.vaultPdaKey,clientDataJSON:r.clientDataJSON,authenticatorData:r.authenticatorData}),o=new C().add(c,u);o.feePayer=this.feePayer.publicKey;let{blockhash:a,lastValidBlockHeight:l}=await this.connection.getLatestBlockhash(this.confirmOptions.commitment);o.recentBlockhash=a,o.sign(this.feePayer);let p=await this.connection.sendRawTransaction(o.serialize(),{skipPreflight:!1,preflightCommitment:this.confirmOptions.preflightCommitment??this.confirmOptions.commitment});return await this.connection.confirmTransaction({signature:p,blockhash:a,lastValidBlockHeight:l},this.confirmOptions.commitment),s}async waitForActiveSessionFinalized(t,n=2e4){let i=Date.now()+n;for(;Date.now()<i;){let s=await this.connection.getAccountInfo(this.vaultPdaKey,"finalized");if(s){let r=s.data,l=84+(r[83]===1?48:0)+32+32;if(r[l]===1){let p=l+1,N=r.slice(p,p+32);if(ne(N,t))return}}await new Promise(r=>setTimeout(r,500))}throw new Error(`register_session_key did not become finalized-visible within ${n}ms`)}};function Se(e){return new P(e)}function ee(){return Math.floor(Math.random()*4294967295)>>>0}async function te(e){if(/^[0-9a-f]{64}$/i.test(e))return ie(e);let{sha256:t}=await import("@noble/hashes/sha256");return t(new TextEncoder().encode(e))}function ne(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(e[n]!==t[n])return!1;return!0}function ie(e){let t=new Uint8Array(e.length/2);for(let n=0;n<t.length;n++)t[n]=parseInt(e.substr(n*2,2),16);return t}export{Se as createSolanaVaultAdapter,Z as deriveChannelId,we as passkeySignerFromP256Keypair};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";var P=Object.create;var y=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var K=Object.getPrototypeOf,C=Object.prototype.hasOwnProperty;var $=(e,t)=>{for(var n in t)y(e,n,{get:t[n],enumerable:!0})},f=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of E(t))!C.call(e,o)&&o!==n&&y(e,o,{get:()=>t[o],enumerable:!(r=k(t,o))||r.enumerable});return e};var R=(e,t,n)=>(n=e!=null?P(K(e)):{},f(t||!e||!e.__esModule?y(n,"default",{value:e,enumerable:!0}):n,e)),N=e=>f(y({},"__esModule",{value:!0}),e);var D={};$(D,{DEFAULT_FACILITATOR_URL:()=>w,SessionScopeExceededError:()=>u,TabClosedError:()=>a,UnsupportedNetworkError:()=>c,atomicToHuman:()=>p,humanToAtomic:()=>h,openTab:()=>U,resumeTab:()=>T});module.exports=N(D);var c=class extends Error{constructor(n){super(`Network ${n} is not yet supported by @dexterai/x402/tab`);this.network=n;this.name="UnsupportedNetworkError"}},u=class extends Error{constructor(n,r){super(`Session scope exceeded: ${n}${r?` (${r})`:""}`);this.reason=n;this.name="SessionScopeExceededError"}},a=class extends Error{constructor(n){super(`Tab ${n} is already closed`);this.channelId=n;this.name="TabClosedError"}};var A=require("@solana/web3.js"),d=require("@noble/hashes/utils");var V=R(require("tweetnacl"),1);var L=(()=>{let e=new Uint8Array(32);return e.set(new TextEncoder().encode("OTS_SESSION_REGISTER_V1"),0),e})(),F=(()=>{let e=new Uint8Array(32);return e.set(new TextEncoder().encode("OTS_SESSION_REVOKE_V1"),0),e})();var x=require("@noble/hashes/sha256");function S(e){let t=new Uint8Array(8);new DataView(t.buffer).setBigUint64(0,e.nonce,!0);let n=new TextEncoder().encode(e.sellerUrl),r=x.sha256.create();return r.update(e.vaultPda.toBytes()),r.update(n),r.update(t),r.digest()}var O=3600,w="https://x402.dexter.cash",v=6;function h(e,t=v){if(!/^\d+(\.\d+)?$/.test(e))throw new Error(`amount must be a non-negative decimal string, got "${e}"`);let[n,r=""]=e.split(".");if(r.length>t)throw new Error(`amount "${e}" has more than ${t} decimals`);let o=r.padEnd(t,"0"),s=`${n}${o}`.replace(/^0+(?=\d)/,"");return s===""?"0":s}function p(e,t=v){if(!/^\d+$/.test(e))throw new Error(`atomic must be a non-negative integer string, got "${e}"`);let n=e.padStart(t+1,"0"),r=n.slice(0,-t).replace(/^0+(?=\d)/,"")||"0",o=n.slice(-t).replace(/0+$/,"");return o?`${r}.${o}`:r}var g=class{channelId;network;internals;cumulativeAtomic=0n;sequenceNumber=0;closed=!1;constructor(t){this.internals=t,this.channelId=t.channelIdHex,this.network=t.network}get state(){let t=this.internals.totalCapAtomic-this.cumulativeAtomic,n=Math.floor(Date.now()/1e3);return{isOpen:!this.closed,spent:p(this.cumulativeAtomic.toString()),remaining:p(t.toString()),expiresInSec:Math.max(0,this.internals.expiresAtUnix-n)}}async signNextVoucher(t){if(this.closed)throw new a(this.channelId);let n=BigInt(t);if(n<=0n)throw new Error(`voucher increment must be > 0, got ${t}`);if(n>this.internals.perUnitCapAtomic)throw new u("cap_exceeded",`single voucher increment ${n} exceeds perUnitCap ${this.internals.perUnitCapAtomic}`);let r=this.cumulativeAtomic+n;if(r>this.internals.totalCapAtomic)throw new u("cap_exceeded",`cumulative ${r} would exceed totalCap ${this.internals.totalCapAtomic}`);this.sequenceNumber+=1,this.cumulativeAtomic=r;let o={channelId:this.channelId,cumulativeAmount:this.cumulativeAtomic.toString(),sequenceNumber:this.sequenceNumber};return await this.internals.vault.signWithSession(this.internals.session,o)}async stream(t,n){if(this.closed)throw new a(this.channelId);let r=await this.signNextVoucher(this.internals.perUnitCapAtomic.toString()),o=Buffer.from(JSON.stringify({payload:r.payload,sessionPublicKey:(0,d.bytesToHex)(r.sessionPublicKey),sessionRegistration:(0,d.bytesToHex)(r.sessionRegistration),sessionSignature:(0,d.bytesToHex)(r.sessionSignature)}),"utf8").toString("base64"),s=new Headers(n?.headers);s.set("X-Tab-Voucher",o),s.set("Accept","text/event-stream");let i=await fetch(t,{...n,headers:s});if(!i.ok){let m=await i.text().catch(()=>"");throw new Error(`tab.stream HTTP ${i.status}: ${m.slice(0,500)}`)}if(!i.body)throw new Error("tab.stream response has no body");return H(i.body)}async close(){if(this.closed)throw new a(this.channelId);return await this.internals.vault.signCloseTab(this.internals.session,this.channelId,this.cumulativeAtomic.toString()),this.closed=!0,this.internals.session.privateKey.fill(0),{settledAmount:p(this.cumulativeAtomic.toString()),settleTx:""}}};async function U(e){if(e.network!==e.vault.network)throw new c(`options.network (${e.network}) doesn't match vault.network (${e.vault.network})`);if(e.network!=="solana:mainnet")throw new c(e.network);let t=BigInt(Math.floor(Math.random()*4294967295)),n=new A.PublicKey(e.vault.vaultPda),r=S({vaultPda:n,sellerUrl:e.seller,nonce:BigInt(t)}),o=(0,d.bytesToHex)(r),s=BigInt(h(e.perUnitCap)),i=BigInt(h(e.totalCap));if(s<=0n)throw new Error("perUnitCap must be > 0");if(i<s)throw new Error("totalCap must be >= perUnitCap");let m=e.sessionDuration??O,l=Math.floor(Date.now()/1e3)+m,b={channelId:o,maxAmountAtomic:i.toString(),expiresAtUnix:l,allowedCounterparty:B(e.seller)},I=await e.vault.authorizeSession(b);return new g({vault:e.vault,network:e.network,seller:e.seller,session:I,channelIdHex:o,channelIdBytes:r,perUnitCapAtomic:s,totalCapAtomic:i,expiresAtUnix:l,facilitatorUrl:e.facilitatorUrl??w})}async function T(e){throw new Error("resumeTab is Phase 3 work. Session keys are memory-only by design; recovery requires reading active_session on chain and re-authorizing. Tracked in dexter-vault roadmap.")}function B(e){if(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(e))try{return new A.PublicKey(e),e}catch{}throw new Error(`seller must be a base58 Solana pubkey for Phase 2 (got "${e}"). URL-based counterparty resolution lands in Phase 3 (seller middleware).`)}async function*H(e){let t=e.getReader(),n=new TextDecoder,r="";try{for(;;){let{done:o,value:s}=await t.read();if(o)break;r+=n.decode(s,{stream:!0});let i;for(;(i=r.indexOf(`
|
|
2
|
+
|
|
3
|
+
`))!==-1;){let m=r.slice(0,i);r=r.slice(i+2);let l=_(m);if(l.eventName==="end")return;if(l.data!==null){let b=l.data.replace(/\\n/g,`
|
|
4
|
+
`);yield new TextEncoder().encode(b)}}}}finally{t.releaseLock()}}function _(e){let t=null,n=[];for(let r of e.split(`
|
|
5
|
+
`))r.startsWith("event:")?t=r.slice(6).trim():r.startsWith("data:")&&n.push(r.slice(5).trimStart());return{eventName:t,data:n.length?n.join(`
|
|
6
|
+
`):null}}0&&(module.exports={DEFAULT_FACILITATOR_URL,SessionScopeExceededError,TabClosedError,UnsupportedNetworkError,atomicToHuman,humanToAtomic,openTab,resumeTab});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { O as OpenTabOptions, T as Tab, R as ResumeTabOptions, H as HumanAmount, A as AtomicAmount } from '../types-DIrmhiD-.cjs';
|
|
2
|
+
export { d as SessionKey, S as SessionScope, g as SessionScopeExceededError, e as SignedVoucher, b as TabCloseResult, h as TabClosedError, c as TabNetworkId, a as TabState, U as UnsupportedNetworkError, f as VaultAdapter, V as VoucherPayload } from '../types-DIrmhiD-.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The `Tab` runtime — the live object returned by `openTab()`.
|
|
6
|
+
*
|
|
7
|
+
* Owns: the session key, the channel id, the cumulative-amount counter,
|
|
8
|
+
* the voucher sequence counter. Exposes: `stream()` for paid streamed
|
|
9
|
+
* requests and `close()` for revocation + settlement.
|
|
10
|
+
*
|
|
11
|
+
* Phase 2 ships `open` and `close` against the live program; `stream()`
|
|
12
|
+
* is the seller-facing surface that lands fully in Phase 3 once the
|
|
13
|
+
* seller middleware is built. For Phase 2, `stream()` throws a clear
|
|
14
|
+
* "phase 3" error — but the open/close round-trip works end to end.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Live Dexter x402 facilitator API. NOT facilitator.dexter.cash —
|
|
18
|
+
* that's a marketing redirect. See reference_dexter_facilitator_url.md. */
|
|
19
|
+
declare const DEFAULT_FACILITATOR_URL = "https://x402.dexter.cash";
|
|
20
|
+
/**
|
|
21
|
+
* Convert a human decimal string ("0.001") to atomic-unit string ("1000")
|
|
22
|
+
* for a 6-decimal token. Rejects negative, scientific, or malformed input.
|
|
23
|
+
*/
|
|
24
|
+
declare function humanToAtomic(human: HumanAmount, decimals?: number): AtomicAmount;
|
|
25
|
+
declare function atomicToHuman(atomic: AtomicAmount, decimals?: number): HumanAmount;
|
|
26
|
+
declare function openTab(options: OpenTabOptions): Promise<Tab>;
|
|
27
|
+
declare function resumeTab(_options: ResumeTabOptions): Promise<Tab>;
|
|
28
|
+
|
|
29
|
+
export { AtomicAmount, DEFAULT_FACILITATOR_URL, HumanAmount, OpenTabOptions, ResumeTabOptions, Tab, atomicToHuman, humanToAtomic, openTab, resumeTab };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { O as OpenTabOptions, T as Tab, R as ResumeTabOptions, H as HumanAmount, A as AtomicAmount } from '../types-DIrmhiD-.js';
|
|
2
|
+
export { d as SessionKey, S as SessionScope, g as SessionScopeExceededError, e as SignedVoucher, b as TabCloseResult, h as TabClosedError, c as TabNetworkId, a as TabState, U as UnsupportedNetworkError, f as VaultAdapter, V as VoucherPayload } from '../types-DIrmhiD-.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The `Tab` runtime — the live object returned by `openTab()`.
|
|
6
|
+
*
|
|
7
|
+
* Owns: the session key, the channel id, the cumulative-amount counter,
|
|
8
|
+
* the voucher sequence counter. Exposes: `stream()` for paid streamed
|
|
9
|
+
* requests and `close()` for revocation + settlement.
|
|
10
|
+
*
|
|
11
|
+
* Phase 2 ships `open` and `close` against the live program; `stream()`
|
|
12
|
+
* is the seller-facing surface that lands fully in Phase 3 once the
|
|
13
|
+
* seller middleware is built. For Phase 2, `stream()` throws a clear
|
|
14
|
+
* "phase 3" error — but the open/close round-trip works end to end.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Live Dexter x402 facilitator API. NOT facilitator.dexter.cash —
|
|
18
|
+
* that's a marketing redirect. See reference_dexter_facilitator_url.md. */
|
|
19
|
+
declare const DEFAULT_FACILITATOR_URL = "https://x402.dexter.cash";
|
|
20
|
+
/**
|
|
21
|
+
* Convert a human decimal string ("0.001") to atomic-unit string ("1000")
|
|
22
|
+
* for a 6-decimal token. Rejects negative, scientific, or malformed input.
|
|
23
|
+
*/
|
|
24
|
+
declare function humanToAtomic(human: HumanAmount, decimals?: number): AtomicAmount;
|
|
25
|
+
declare function atomicToHuman(atomic: AtomicAmount, decimals?: number): HumanAmount;
|
|
26
|
+
declare function openTab(options: OpenTabOptions): Promise<Tab>;
|
|
27
|
+
declare function resumeTab(_options: ResumeTabOptions): Promise<Tab>;
|
|
28
|
+
|
|
29
|
+
export { AtomicAmount, DEFAULT_FACILITATOR_URL, HumanAmount, OpenTabOptions, ResumeTabOptions, Tab, atomicToHuman, humanToAtomic, openTab, resumeTab };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
var u=class extends Error{constructor(r){super(`Network ${r} is not yet supported by @dexterai/x402/tab`);this.network=r;this.name="UnsupportedNetworkError"}},l=class extends Error{constructor(r,t){super(`Session scope exceeded: ${r}${t?` (${t})`:""}`);this.reason=r;this.name="SessionScopeExceededError"}},a=class extends Error{constructor(r){super(`Tab ${r} is already closed`);this.channelId=r;this.name="TabClosedError"}};import{PublicKey as A}from"@solana/web3.js";import{bytesToHex as d}from"@noble/hashes/utils";import V from"tweetnacl";var K=(()=>{let e=new Uint8Array(32);return e.set(new TextEncoder().encode("OTS_SESSION_REGISTER_V1"),0),e})(),C=(()=>{let e=new Uint8Array(32);return e.set(new TextEncoder().encode("OTS_SESSION_REVOKE_V1"),0),e})();import{sha256 as S}from"@noble/hashes/sha256";function g(e){let n=new Uint8Array(8);new DataView(n.buffer).setBigUint64(0,e.nonce,!0);let r=new TextEncoder().encode(e.sellerUrl),t=S.create();return t.update(e.vaultPda.toBytes()),t.update(r),t.update(n),t.digest()}var v=3600,w="https://x402.dexter.cash",f=6;function h(e,n=f){if(!/^\d+(\.\d+)?$/.test(e))throw new Error(`amount must be a non-negative decimal string, got "${e}"`);let[r,t=""]=e.split(".");if(t.length>n)throw new Error(`amount "${e}" has more than ${n} decimals`);let o=t.padEnd(n,"0"),s=`${r}${o}`.replace(/^0+(?=\d)/,"");return s===""?"0":s}function p(e,n=f){if(!/^\d+$/.test(e))throw new Error(`atomic must be a non-negative integer string, got "${e}"`);let r=e.padStart(n+1,"0"),t=r.slice(0,-n).replace(/^0+(?=\d)/,"")||"0",o=r.slice(-n).replace(/0+$/,"");return o?`${t}.${o}`:t}var b=class{channelId;network;internals;cumulativeAtomic=0n;sequenceNumber=0;closed=!1;constructor(n){this.internals=n,this.channelId=n.channelIdHex,this.network=n.network}get state(){let n=this.internals.totalCapAtomic-this.cumulativeAtomic,r=Math.floor(Date.now()/1e3);return{isOpen:!this.closed,spent:p(this.cumulativeAtomic.toString()),remaining:p(n.toString()),expiresInSec:Math.max(0,this.internals.expiresAtUnix-r)}}async signNextVoucher(n){if(this.closed)throw new a(this.channelId);let r=BigInt(n);if(r<=0n)throw new Error(`voucher increment must be > 0, got ${n}`);if(r>this.internals.perUnitCapAtomic)throw new l("cap_exceeded",`single voucher increment ${r} exceeds perUnitCap ${this.internals.perUnitCapAtomic}`);let t=this.cumulativeAtomic+r;if(t>this.internals.totalCapAtomic)throw new l("cap_exceeded",`cumulative ${t} would exceed totalCap ${this.internals.totalCapAtomic}`);this.sequenceNumber+=1,this.cumulativeAtomic=t;let o={channelId:this.channelId,cumulativeAmount:this.cumulativeAtomic.toString(),sequenceNumber:this.sequenceNumber};return await this.internals.vault.signWithSession(this.internals.session,o)}async stream(n,r){if(this.closed)throw new a(this.channelId);let t=await this.signNextVoucher(this.internals.perUnitCapAtomic.toString()),o=Buffer.from(JSON.stringify({payload:t.payload,sessionPublicKey:d(t.sessionPublicKey),sessionRegistration:d(t.sessionRegistration),sessionSignature:d(t.sessionSignature)}),"utf8").toString("base64"),s=new Headers(r?.headers);s.set("X-Tab-Voucher",o),s.set("Accept","text/event-stream");let i=await fetch(n,{...r,headers:s});if(!i.ok){let m=await i.text().catch(()=>"");throw new Error(`tab.stream HTTP ${i.status}: ${m.slice(0,500)}`)}if(!i.body)throw new Error("tab.stream response has no body");return P(i.body)}async close(){if(this.closed)throw new a(this.channelId);return await this.internals.vault.signCloseTab(this.internals.session,this.channelId,this.cumulativeAtomic.toString()),this.closed=!0,this.internals.session.privateKey.fill(0),{settledAmount:p(this.cumulativeAtomic.toString()),settleTx:""}}};async function U(e){if(e.network!==e.vault.network)throw new u(`options.network (${e.network}) doesn't match vault.network (${e.vault.network})`);if(e.network!=="solana:mainnet")throw new u(e.network);let n=BigInt(Math.floor(Math.random()*4294967295)),r=new A(e.vault.vaultPda),t=g({vaultPda:r,sellerUrl:e.seller,nonce:BigInt(n)}),o=d(t),s=BigInt(h(e.perUnitCap)),i=BigInt(h(e.totalCap));if(s<=0n)throw new Error("perUnitCap must be > 0");if(i<s)throw new Error("totalCap must be >= perUnitCap");let m=e.sessionDuration??v,c=Math.floor(Date.now()/1e3)+m,y={channelId:o,maxAmountAtomic:i.toString(),expiresAtUnix:c,allowedCounterparty:I(e.seller)},x=await e.vault.authorizeSession(y);return new b({vault:e.vault,network:e.network,seller:e.seller,session:x,channelIdHex:o,channelIdBytes:t,perUnitCapAtomic:s,totalCapAtomic:i,expiresAtUnix:c,facilitatorUrl:e.facilitatorUrl??w})}async function T(e){throw new Error("resumeTab is Phase 3 work. Session keys are memory-only by design; recovery requires reading active_session on chain and re-authorizing. Tracked in dexter-vault roadmap.")}function I(e){if(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(e))try{return new A(e),e}catch{}throw new Error(`seller must be a base58 Solana pubkey for Phase 2 (got "${e}"). URL-based counterparty resolution lands in Phase 3 (seller middleware).`)}async function*P(e){let n=e.getReader(),r=new TextDecoder,t="";try{for(;;){let{done:o,value:s}=await n.read();if(o)break;t+=r.decode(s,{stream:!0});let i;for(;(i=t.indexOf(`
|
|
2
|
+
|
|
3
|
+
`))!==-1;){let m=t.slice(0,i);t=t.slice(i+2);let c=k(m);if(c.eventName==="end")return;if(c.data!==null){let y=c.data.replace(/\\n/g,`
|
|
4
|
+
`);yield new TextEncoder().encode(y)}}}}finally{n.releaseLock()}}function k(e){let n=null,r=[];for(let t of e.split(`
|
|
5
|
+
`))t.startsWith("event:")?n=t.slice(6).trim():t.startsWith("data:")&&r.push(t.slice(5).trimStart());return{eventName:n,data:r.length?r.join(`
|
|
6
|
+
`):null}}export{w as DEFAULT_FACILITATOR_URL,l as SessionScopeExceededError,a as TabClosedError,u as UnsupportedNetworkError,p as atomicToHuman,h as humanToAtomic,U as openTab,T as resumeTab};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";var te=Object.create;var x=Object.defineProperty;var ne=Object.getOwnPropertyDescriptor;var re=Object.getOwnPropertyNames;var oe=Object.getPrototypeOf,ie=Object.prototype.hasOwnProperty;var se=(e,t)=>{for(var n in t)x(e,n,{get:t[n],enumerable:!0})},z=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of re(t))!ie.call(e,o)&&o!==n&&x(e,o,{get:()=>t[o],enumerable:!(r=ne(t,o))||r.enumerable});return e};var F=(e,t,n)=>(n=e!=null?te(oe(e)):{},z(t||!e||!e.__esModule?x(n,"default",{value:e,enumerable:!0}):n,e)),ae=e=>z(x({},"__esModule",{value:!0}),e);var be={};se(be,{FileVoucherStore:()=>_,InMemoryVoucherStore:()=>b,InvalidRegistrationError:()=>m,InvalidVoucherError:()=>l,InvalidVoucherSignatureError:()=>g,OnChainVerificationError:()=>h,ScopeViolationError:()=>u,TAB_VOUCHER_HEADER:()=>C,enforceScope:()=>k,openSse:()=>Q,parseRegistration:()=>I,readVaultState:()=>B,requireTab:()=>X,tabMiddleware:()=>Z,verifyRegistrationOnChain:()=>E,verifyVoucherSignature:()=>T});module.exports=ae(be);var l=class extends Error{constructor(n,r){super(`Invalid voucher: ${n}${r?` (${r})`:""}`);this.reason=n;this.name="InvalidVoucherError"}};var W=require("@solana/web3.js");var G=F(require("tweetnacl"),1),ue=require("@noble/hashes/sha256"),ce=require("@noble/curves/p256"),U=require("@solana/web3.js");var ve=(()=>{let e=new Uint8Array(32);return e.set(new TextEncoder().encode("OTS_SESSION_REGISTER_V1"),0),e})(),xe=(()=>{let e=new Uint8Array(32);return e.set(new TextEncoder().encode("OTS_SESSION_REVOKE_V1"),0),e})();function K(e){if(e.channelId.length!==32)throw new Error(`channelId must be 32 bytes, got ${e.channelId.length}`);let t=new Uint8Array(44),n=new DataView(t.buffer),r=0;if(t.set(e.channelId,r),r+=32,n.setBigUint64(r,e.cumulativeAmount,!0),r+=8,n.setUint32(r,e.sequenceNumber>>>0,!0),r+=4,r!==44)throw new Error(`internal: voucher payload wrong length ${r}, expected 44`);return t}var A=require("@solana/web3.js"),P=new A.PublicKey("Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc"),Ue=new A.PublicKey("Secp256r1SigVerify1111111111111111111111111"),Ie=new A.PublicKey("Sysvar1nstructions1111111111111111111111111"),Ee=new Uint8Array([69,94,60,44,49,199,183,233]),Te=new Uint8Array([81,192,32,110,104,116,144,151]);var O="OTS_SESSION_REGISTER_V1",m=class extends Error{constructor(n,r){super(`Invalid registration: ${n}${r?` (${r})`:""}`);this.reason=n;this.name="InvalidRegistrationError"}};function I(e){if(e.length!==180)throw new m("wrong_length",`expected 180, got ${e.length}`);let t=new TextDecoder().decode(e.slice(0,O.length));if(t!==O)throw new m("wrong_domain",`got "${t}"`);for(let s=O.length;s<32;s++)if(e[s]!==0)throw new m("wrong_domain",`non-NUL padding at byte ${s}`);let n=new DataView(e.buffer,e.byteOffset,e.byteLength),r=new U.PublicKey(e.slice(32,64)),o=new U.PublicKey(e.slice(64,96)),c=e.slice(96,128),p=n.getBigUint64(128,!0),f=n.getBigInt64(136,!0),i=new U.PublicKey(e.slice(144,176)),a=n.getUint32(176,!0);if(!r.equals(P))throw new m("wrong_program",`${r.toBase58()} is not ${P.toBase58()}`);if(p===0n)throw new m("cap_zero");let d=BigInt(Math.floor(Date.now()/1e3));if(f<=d)throw new m("expiry_in_past",`expires_at=${f}, now=${d}`);return{programId:r,vaultPda:o,sessionPubkey:new Uint8Array(c),maxAmount:p,expiresAt:f,allowedCounterparty:i,nonce:a}}var j=10,h=class extends Error{constructor(n,r){super(`On-chain verification failed: ${n}${r?` (${r})`:""}`);this.reason=n;this.name="OnChainVerificationError"}};async function B(e,t){let n=await e.getAccountInfo(t,"finalized");if(!n)throw new h("vault_not_found",t.toBase58());if(!n.owner.equals(P))throw new h("wrong_program",`owner ${n.owner.toBase58()} is not the vault program`);let r=n.data,o=new Uint8Array(r.slice(j,j+33)),a=84+(r[83]===1?48:0)+32+32;if(r[a]!==1)return{passkeyPubkey:o,activeSessionPubkey:null};let s=a+1,y=new Uint8Array(r.slice(s,s+32));return{passkeyPubkey:o,activeSessionPubkey:y}}async function E(e,t){let n=await B(e,t.vaultPda);if(n.activeSessionPubkey===null)throw new h("session_not_active","vault has no active_session \u2014 was it revoked?");if(!le(n.activeSessionPubkey,t.sessionPubkey))throw new h("session_pubkey_mismatch",`on-chain ${J(n.activeSessionPubkey)} != registration ${J(t.sessionPubkey)}`);return{passkeyPubkey:n.passkeyPubkey}}var g=class extends Error{constructor(t){super(`Invalid voucher signature${t?`: ${t}`:""}`),this.name="InvalidVoucherSignatureError"}};function T(e,t){if(t.length!==32)throw new g(`channelIdBytes must be 32 bytes, got ${t.length}`);if(e.sessionPublicKey.length!==32)throw new g(`sessionPublicKey must be 32 bytes, got ${e.sessionPublicKey.length}`);if(e.sessionSignature.length!==64)throw new g(`sessionSignature must be 64 bytes, got ${e.sessionSignature.length}`);let n=K({channelId:t,cumulativeAmount:BigInt(e.payload.cumulativeAmount),sequenceNumber:e.payload.sequenceNumber});if(!G.default.sign.detached.verify(n,e.sessionSignature,e.sessionPublicKey))throw new g("ed25519 verify rejected")}var u=class extends Error{constructor(n,r){super(`Scope violation: ${n}${r?` (${r})`:""}`);this.reason=n;this.name="ScopeViolationError"}};function k(e){let t=BigInt(e.voucher.payload.cumulativeAmount);if(t>e.registration.maxAmount)throw new u("cumulative_exceeds_cap",`${t} > ${e.registration.maxAmount}`);let n=BigInt(Math.floor(Date.now()/1e3));if(n>=e.registration.expiresAt)throw new u("session_expired",`now=${n} >= expiresAt=${e.registration.expiresAt}`);if(!e.registration.allowedCounterparty.equals(e.expectedCounterparty))throw new u("wrong_counterparty",`${e.registration.allowedCounterparty.toBase58()} != ${e.expectedCounterparty.toBase58()}`);if(e.previousCumulativeAtomic!==void 0){let r=BigInt(e.previousCumulativeAtomic);if(t<=r)throw new u("non_monotonic",`cumulative=${t} not > previous=${r}`)}}function le(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(e[n]!==t[n])return!1;return!0}function J(e){let t="";for(let n of e)t+=n.toString(16).padStart(2,"0");return t}var w=require("fs"),R=require("path");function me(e){return{payload:e.payload,sessionPublicKey:N(e.sessionPublicKey),sessionRegistration:N(e.sessionRegistration),sessionSignature:N(e.sessionSignature)}}function de(e){return{payload:e.payload,sessionPublicKey:D(e.sessionPublicKey),sessionRegistration:D(e.sessionRegistration),sessionSignature:D(e.sessionSignature)}}function N(e){let t="";for(let n of e)t+=n.toString(16).padStart(2,"0");return t}function D(e){if(e.length%2!==0)throw new Error(`hex length must be even, got ${e.length}`);let t=new Uint8Array(e.length/2);for(let n=0;n<t.length;n++)t[n]=parseInt(e.substr(n*2,2),16);return t}var b=class{map=new Map;async get(t){return this.map.get(t)??null}async set(t,n){this.map.set(t,n)}async delete(t){this.map.delete(t)}},_=class{constructor(t){this.dir=t}pathFor(t){if(!/^[a-z0-9_-]+$/i.test(t))throw new Error(`unsafe channelId for filesystem: ${t}`);return(0,R.join)(this.dir,`${t}.json`)}async get(t){try{let n=await w.promises.readFile(this.pathFor(t),"utf8");return de(JSON.parse(n))}catch(n){if(n?.code==="ENOENT")return null;throw n}}async set(t,n){let r=this.pathFor(t);await w.promises.mkdir((0,R.dirname)(r),{recursive:!0});let o=`${r}.tmp`;await w.promises.writeFile(o,JSON.stringify(me(n))),await w.promises.rename(o,r)}async delete(t){try{await w.promises.unlink(this.pathFor(t))}catch(n){if(n?.code!=="ENOENT")throw n}}};var ge=require("@solana/web3.js"),he=require("@noble/hashes/utils");var pe=F(require("tweetnacl"),1);var ye=require("@noble/hashes/sha256");var Y=6;function S(e,t=Y){if(!/^\d+(\.\d+)?$/.test(e))throw new Error(`amount must be a non-negative decimal string, got "${e}"`);let[n,r=""]=e.split(".");if(r.length>t)throw new Error(`amount "${e}" has more than ${t} decimals`);let o=r.padEnd(t,"0"),c=`${n}${o}`.replace(/^0+(?=\d)/,"");return c===""?"0":c}function v(e,t=Y){if(!/^\d+$/.test(e))throw new Error(`atomic must be a non-negative integer string, got "${e}"`);let n=e.padStart(t+1,"0"),r=n.slice(0,-t).replace(/^0+(?=\d)/,"")||"0",o=n.slice(-t).replace(/0+$/,"");return o?`${r}.${o}`:r}var C="x-tab-voucher",H=class{map=new Map;get(t){return this.map.get(t)}set(t,n){this.map.set(t,n)}update(t,n){let r=this.map.get(t);r&&(r.lastCumulativeAtomic=n)}delete(t){this.map.delete(t)}},M=class{constructor(t,n,r,o){this.chargeImpl=o;this.channelId=t,this.network=n,this.cumulativeAtomic=r}channelId;network;sessionPublicKey=null;cumulativeAtomic;cumulative(){return v(this.cumulativeAtomic.toString())}bumpCumulative(t){this.cumulativeAtomic=t}setSessionPublicKey(t){this.sessionPublicKey=t}async charge(t){return this.chargeImpl(t)}};function fe(e){if(typeof e!="string"||e.length===0)throw new l("signature_invalid",`missing ${C} header`);let t;try{t=Buffer.from(e,"base64").toString("utf8")}catch{throw new l("signature_invalid","malformed base64")}let n;try{n=JSON.parse(t)}catch{throw new l("signature_invalid","malformed JSON")}if(!n||typeof n!="object"||!n.payload||!n.sessionPublicKey)throw new l("signature_invalid","missing required fields");return{payload:n.payload,sessionPublicKey:V(n.sessionPublicKey),sessionRegistration:V(n.sessionRegistration),sessionSignature:V(n.sessionSignature)}}function V(e){if(typeof e!="string"||e.length%2!==0)throw new l("signature_invalid",`bad hex: ${typeof e}`);let t=new Uint8Array(e.length/2);for(let n=0;n<t.length;n++)t[n]=parseInt(e.substr(n*2,2),16);return t}function we(e){if(!/^[0-9a-f]{64}$/i.test(e))throw new l("signature_invalid",`channelId must be 64-char hex, got "${e}"`);return V(e)}function Z(e){let t=e.store??new b,n=new H,r=typeof e.sellerPubkey=="string"?new W.PublicKey(e.sellerPubkey):e.sellerPubkey,o=e.maxPerVoucherAtomic?BigInt(e.maxPerVoucherAtomic):BigInt(S(e.perUnit))*100n;return async(c,p,f)=>{try{let i=fe(c.headers[C]),a=i.payload.channelId,d=we(a),s=n.get(a);if(!s){let $=I(i.sessionRegistration);await E(e.connection,$),s={registration:$,lastCumulativeAtomic:"0"},n.set(a,s)}T(i,d),k({registration:s.registration,voucher:i,expectedCounterparty:r,previousCumulativeAtomic:s.lastCumulativeAtomic});let y=BigInt(i.payload.cumulativeAmount),ee=BigInt(s.lastCumulativeAtomic),q=y-ee;if(q>o)throw new u("cumulative_exceeds_cap",`single voucher increment ${q} exceeds maxPerVoucherAtomic ${o}`);await t.set(a,i),n.update(a,i.payload.cumulativeAmount);let L=new M(a,e.network,y,async $=>{throw new Error("SellerTab.charge() is not driven by the route handler; the buyer presents a fresh voucher per chunk. Use openSse(res, tab) for the metered-stream pattern.")});L.setSessionPublicKey(i.sessionPublicKey),c.tab=L,f()}catch(i){if(i instanceof l||i instanceof m||i instanceof h||i instanceof g||i instanceof u){p.status(402).json({error:"invalid_voucher",reason:i.reason??"unknown",detail:i.message});return}f(i)}}}function X(e){if(!e.tab)throw new Error("req.tab is missing \u2014 did tabMiddleware run on this route?");return e.tab}function Q(e,t){if(!t.tab)throw new Error("openSse requires options.tab");e.headersSent||(e.setHeader("Content-Type","text/event-stream"),e.setHeader("Cache-Control","no-cache"),e.setHeader("Connection","keep-alive"),typeof e.flushHeaders=="function"&&e.flushHeaders());let n=t.tab,r=BigInt(S(n.cumulative())),o=t.perUnit?BigInt(S(t.perUnit)):null,c=0n,p=!1;function f(d=1){if(p)return Promise.reject(new Error("meter ended"));if(o===null)return Promise.reject(new Error("charge() needs options.perUnit"));let s=o*BigInt(d),y=c+s;return y>r?Promise.reject(new u("cumulative_exceeds_cap",`chunk would push request total to ${v(y.toString())} beyond voucher-authorized budget ${v(r.toString())}`)):(c=y,Promise.resolve())}function i(d){if(p)throw new Error("meter ended");let y=(typeof d=="string"?d:Buffer.from(d).toString("utf8")).replace(/\n/g,"\\n");e.write(`data: ${y}
|
|
2
|
+
|
|
3
|
+
`)}function a(){p||(p=!0,e.write(`event: end
|
|
4
|
+
data: {"chargedAtomic":"${c}"}
|
|
5
|
+
|
|
6
|
+
`),e.end())}return{charge:f,send:i,end:a}}0&&(module.exports={FileVoucherStore,InMemoryVoucherStore,InvalidRegistrationError,InvalidVoucherError,InvalidVoucherSignatureError,OnChainVerificationError,ScopeViolationError,TAB_VOUCHER_HEADER,enforceScope,openSse,parseRegistration,readVaultState,requireTab,tabMiddleware,verifyRegistrationOnChain,verifyVoucherSignature});
|