@dexterai/x402 3.11.0 → 3.11.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/dist/tab/index.cjs +4 -4
- package/dist/tab/index.d.cts +4 -0
- package/dist/tab/index.d.ts +4 -0
- package/dist/tab/index.js +4 -4
- package/package.json +1 -1
package/dist/tab/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var K=Object.create;var b=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var $=Object.getPrototypeOf,N=Object.prototype.hasOwnProperty;var O=(e,t)=>{for(var n in t)b(e,n,{get:t[n],enumerable:!0})},v=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of V(t))!N.call(e,s)&&s!==n&&b(e,s,{get:()=>t[s],enumerable:!(r=E(t,s))||r.enumerable});return e};var _=(e,t,n)=>(n=e!=null?K($(e)):{},v(t||!e||!e.__esModule?b(n,"default",{value:e,enumerable:!0}):n,e)),M=e=>v(b({},"__esModule",{value:!0}),e);var J={};O(J,{DEFAULT_FACILITATOR_URL:()=>x,DEXTER_VAULT_PROGRAM_ID:()=>u.DEXTER_VAULT_PROGRAM_ID,INSTRUCTIONS_SYSVAR_ID:()=>u.INSTRUCTIONS_SYSVAR_ID,SECP256R1_PROGRAM_ID:()=>u.SECP256R1_PROGRAM_ID,SessionScopeExceededError:()=>p,TabClosedError:()=>l,UnsupportedNetworkError:()=>m,atomicToHuman:()=>g,buildRegisterSessionKeyInstruction:()=>y.buildRegisterSessionKeyInstruction,buildRevokeSessionKeyInstruction:()=>y.buildRevokeSessionKeyInstruction,buildSecp256r1VerifyInstruction:()=>T.buildSecp256r1VerifyInstruction,buildVoucherMessage:()=>a.buildVoucherMessage,humanToAtomic:()=>A,openTab:()=>k,resumeTab:()=>P,sessionRegisterMessage:()=>a.sessionRegisterMessage,sessionRevokeMessage:()=>a.sessionRevokeMessage,voucherPayloadMessage:()=>a.voucherPayloadMessage});module.exports=M(J);var m=class extends Error{constructor(n){super(`Network ${n} is not yet supported by @dexterai/x402/tab`);this.network=n;this.name="UnsupportedNetworkError"}},p=class extends Error{constructor(n,r){super(`Session scope exceeded: ${n}${r?` (${r})`:""}`);this.reason=n;this.name="SessionScopeExceededError"}},l=class extends Error{constructor(n){super(`Tab ${n} is already closed`);this.channelId=n;this.name="TabClosedError"}};var f=require("@solana/web3.js"),d=require("@noble/hashes/utils");var H=_(require("tweetnacl"),1);var a=require("@dexterai/vault/messages");var I=require("@noble/hashes/sha256");function R(e){let t=new Uint8Array(8);new DataView(t.buffer).setBigUint64(0,e.nonce,!0);let n=new TextEncoder().encode(e.sellerUrl),r=I.sha256.create();return r.update(e.vaultPda.toBytes()),r.update(n),r.update(t),r.digest()}var D=3600,x="https://x402.dexter.cash",U=6;function A(e,t=U){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 s=r.padEnd(t,"0"),o=`${n}${s}`.replace(/^0+(?=\d)/,"");return o===""?"0":o}function g(e,t=U){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",s=n.slice(-t).replace(/0+$/,"");return s?`${r}.${s}`:r}var w=class{channelId;network;internals;cumulativeAtomic=0n;sequenceNumber=0;closed=!1;lastSignedVoucher=null;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:g(this.cumulativeAtomic.toString()),remaining:g(t.toString()),expiresInSec:Math.max(0,this.internals.expiresAtUnix-n)}}async signNextVoucher(t){if(this.closed)throw new l(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 p("cap_exceeded",`single voucher increment ${n} exceeds perUnitCap ${this.internals.perUnitCapAtomic}`);let r=this.cumulativeAtomic+n;if(r>this.internals.totalCapAtomic)throw new p("cap_exceeded",`cumulative ${r} would exceed totalCap ${this.internals.totalCapAtomic}`);this.sequenceNumber+=1,this.cumulativeAtomic=r;let s={channelId:this.channelId,cumulativeAmount:this.cumulativeAtomic.toString(),sequenceNumber:this.sequenceNumber},o=await this.internals.vault.signWithSession(this.internals.session,s);return this.lastSignedVoucher=o,o}async stream(t,n){if(this.closed)throw new l(this.channelId);let r=await this.signNextVoucher(this.internals.perUnitCapAtomic.toString()),s=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"),o=new Headers(n?.headers);o.set("X-Tab-Voucher",s),o.set("Accept","text/event-stream");let i=await fetch(t,{...n,headers:o});if(!i.ok){let c=await i.text().catch(()=>"");throw new Error(`tab.stream HTTP ${i.status}: ${c.slice(0,500)}`)}if(!i.body)throw new Error("tab.stream response has no body");return q(i.body)}async close(){if(this.closed)throw new l(this.channelId);let t="";return this.lastSignedVoucher&&this.cumulativeAtomic>0n&&(t=await B(this.internals.facilitatorUrl,this.lastSignedVoucher,this.internals.network)),await this.internals.vault.signCloseTab(this.internals.session,this.channelId,this.cumulativeAtomic.toString()),this.closed=!0,this.internals.session.privateKey.fill(0),{settledAmount:g(this.cumulativeAtomic.toString()),settleTx:t}}};async function B(e,t,n){let r=`${e.replace(/\/$/,"")}/tab/settle`,s={channelId:t.payload.channelId,cumulativeAmount:t.payload.cumulativeAmount,sequenceNumber:t.payload.sequenceNumber,sessionPublicKey:(0,d.bytesToHex)(t.sessionPublicKey),sessionSignature:(0,d.bytesToHex)(t.sessionSignature),sessionRegistration:(0,d.bytesToHex)(t.sessionRegistration),network:n},o=await fetch(r,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(s)}),i=await o.text();if(!o.ok)throw new Error(`tab settle ${o.status}: ${i.slice(0,500)}`);let c;try{c=JSON.parse(i)}catch{throw new Error(`tab settle returned non-JSON: ${i.slice(0,200)}`)}if(!c.settleTx)throw new Error(`tab settle returned no settleTx: ${i.slice(0,200)}`);return c.settleTx}async function k(e){if(e.network!==e.vault.network)throw new m(`options.network (${e.network}) doesn't match vault.network (${e.vault.network})`);if(e.network!=="solana:mainnet")throw new m(e.network);let t=BigInt(Math.floor(Math.random()*4294967295)),n=new f.PublicKey(e.vault.vaultPda),r=R({vaultPda:n,sellerUrl:e.seller,nonce:BigInt(t)}),s=(0,d.bytesToHex)(r),o=BigInt(A(e.perUnitCap)),i=BigInt(A(e.totalCap));if(o<=0n)throw new Error("perUnitCap must be > 0");if(i<o)throw new Error("totalCap must be >= perUnitCap");let c=e.sessionDuration??D,h=Math.floor(Date.now()/1e3)+c,S={channelId:s,maxAmountAtomic:i.toString(),expiresAtUnix:h,allowedCounterparty:L(e.seller)},C=await e.vault.authorizeSession(S);return new w({vault:e.vault,network:e.network,seller:e.seller,session:C,channelIdHex:s,channelIdBytes:r,perUnitCapAtomic:o,totalCapAtomic:i,expiresAtUnix:h,facilitatorUrl:e.facilitatorUrl??x})}async function P(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 L(e){if(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(e))try{return new f.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*q(e){let t=e.getReader(),n=new TextDecoder,r="";try{for(;;){let{done:s,value:o}=await t.read();if(s)break;r+=n.decode(o,{stream:!0});let i;for(;(i=r.indexOf(`
|
|
2
2
|
|
|
3
|
-
`))!==-1;){let
|
|
4
|
-
`);yield new TextEncoder().encode(
|
|
3
|
+
`))!==-1;){let c=r.slice(0,i);r=r.slice(i+2);let h=F(c);if(h.eventName==="end")return;if(h.data!==null){let S=h.data.replace(/\\n/g,`
|
|
4
|
+
`);yield new TextEncoder().encode(S)}}}}finally{t.releaseLock()}}function F(e){let t=null,n=[];for(let r of e.split(`
|
|
5
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});
|
|
6
|
+
`):null}}var y=require("@dexterai/vault/instructions"),T=require("@dexterai/vault/precompile"),u=require("@dexterai/vault/constants");0&&(module.exports={DEFAULT_FACILITATOR_URL,DEXTER_VAULT_PROGRAM_ID,INSTRUCTIONS_SYSVAR_ID,SECP256R1_PROGRAM_ID,SessionScopeExceededError,TabClosedError,UnsupportedNetworkError,atomicToHuman,buildRegisterSessionKeyInstruction,buildRevokeSessionKeyInstruction,buildSecp256r1VerifyInstruction,buildVoucherMessage,humanToAtomic,openTab,resumeTab,sessionRegisterMessage,sessionRevokeMessage,voucherPayloadMessage});
|
package/dist/tab/index.d.cts
CHANGED
|
@@ -2,6 +2,10 @@ import { O as OpenTabOptions, T as Tab, R as ResumeTabOptions } from '../types-B
|
|
|
2
2
|
export { S as SessionScopeExceededError, b as TabCloseResult, c as TabClosedError, a as TabState, U as UnsupportedNetworkError, V as VaultAdapter } from '../types-BtibqM00.cjs';
|
|
3
3
|
import { HumanAmount, AtomicAmount } from '@dexterai/vault/types';
|
|
4
4
|
export { AtomicAmount, HumanAmount, SessionKey, SessionScope, SignedVoucher, TabNetworkId, VoucherPayload } from '@dexterai/vault/types';
|
|
5
|
+
export { SessionRegisterMessageArgs, SessionRevokeMessageArgs, VoucherPayloadBytes, buildVoucherMessage, sessionRegisterMessage, sessionRevokeMessage, voucherPayloadMessage } from '@dexterai/vault/messages';
|
|
6
|
+
export { BuildRegisterSessionKeyArgs, BuildRevokeSessionKeyArgs, buildRegisterSessionKeyInstruction, buildRevokeSessionKeyInstruction } from '@dexterai/vault/instructions';
|
|
7
|
+
export { buildSecp256r1VerifyInstruction } from '@dexterai/vault/precompile';
|
|
8
|
+
export { DEXTER_VAULT_PROGRAM_ID, INSTRUCTIONS_SYSVAR_ID, SECP256R1_PROGRAM_ID } from '@dexterai/vault/constants';
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* The `Tab` runtime — the live object returned by `openTab()`.
|
package/dist/tab/index.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import { O as OpenTabOptions, T as Tab, R as ResumeTabOptions } from '../types-B
|
|
|
2
2
|
export { S as SessionScopeExceededError, b as TabCloseResult, c as TabClosedError, a as TabState, U as UnsupportedNetworkError, V as VaultAdapter } from '../types-BtibqM00.js';
|
|
3
3
|
import { HumanAmount, AtomicAmount } from '@dexterai/vault/types';
|
|
4
4
|
export { AtomicAmount, HumanAmount, SessionKey, SessionScope, SignedVoucher, TabNetworkId, VoucherPayload } from '@dexterai/vault/types';
|
|
5
|
+
export { SessionRegisterMessageArgs, SessionRevokeMessageArgs, VoucherPayloadBytes, buildVoucherMessage, sessionRegisterMessage, sessionRevokeMessage, voucherPayloadMessage } from '@dexterai/vault/messages';
|
|
6
|
+
export { BuildRegisterSessionKeyArgs, BuildRevokeSessionKeyArgs, buildRegisterSessionKeyInstruction, buildRevokeSessionKeyInstruction } from '@dexterai/vault/instructions';
|
|
7
|
+
export { buildSecp256r1VerifyInstruction } from '@dexterai/vault/precompile';
|
|
8
|
+
export { DEXTER_VAULT_PROGRAM_ID, INSTRUCTIONS_SYSVAR_ID, SECP256R1_PROGRAM_ID } from '@dexterai/vault/constants';
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* The `Tab` runtime — the live object returned by `openTab()`.
|
package/dist/tab/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
var
|
|
1
|
+
var d=class extends Error{constructor(r){super(`Network ${r} is not yet supported by @dexterai/x402/tab`);this.network=r;this.name="UnsupportedNetworkError"}},m=class extends Error{constructor(r,n){super(`Session scope exceeded: ${r}${n?` (${n})`:""}`);this.reason=r;this.name="SessionScopeExceededError"}},c=class extends Error{constructor(r){super(`Tab ${r} is already closed`);this.channelId=r;this.name="TabClosedError"}};import{PublicKey as S}from"@solana/web3.js";import{bytesToHex as u}from"@noble/hashes/utils";import F from"tweetnacl";import{sessionRegisterMessage as T,sessionRevokeMessage as v,voucherPayloadMessage as b,buildVoucherMessage as I}from"@dexterai/vault/messages";import{sha256 as R}from"@noble/hashes/sha256";function A(e){let t=new Uint8Array(8);new DataView(t.buffer).setBigUint64(0,e.nonce,!0);let r=new TextEncoder().encode(e.sellerUrl),n=R.create();return n.update(e.vaultPda.toBytes()),n.update(r),n.update(t),n.digest()}var U=3600,w="https://x402.dexter.cash",f=6;function y(e,t=f){if(!/^\d+(\.\d+)?$/.test(e))throw new Error(`amount must be a non-negative decimal string, got "${e}"`);let[r,n=""]=e.split(".");if(n.length>t)throw new Error(`amount "${e}" has more than ${t} decimals`);let i=n.padEnd(t,"0"),s=`${r}${i}`.replace(/^0+(?=\d)/,"");return s===""?"0":s}function p(e,t=f){if(!/^\d+$/.test(e))throw new Error(`atomic must be a non-negative integer string, got "${e}"`);let r=e.padStart(t+1,"0"),n=r.slice(0,-t).replace(/^0+(?=\d)/,"")||"0",i=r.slice(-t).replace(/0+$/,"");return i?`${n}.${i}`:n}var g=class{channelId;network;internals;cumulativeAtomic=0n;sequenceNumber=0;closed=!1;lastSignedVoucher=null;constructor(t){this.internals=t,this.channelId=t.channelIdHex,this.network=t.network}get state(){let t=this.internals.totalCapAtomic-this.cumulativeAtomic,r=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-r)}}async signNextVoucher(t){if(this.closed)throw new c(this.channelId);let r=BigInt(t);if(r<=0n)throw new Error(`voucher increment must be > 0, got ${t}`);if(r>this.internals.perUnitCapAtomic)throw new m("cap_exceeded",`single voucher increment ${r} exceeds perUnitCap ${this.internals.perUnitCapAtomic}`);let n=this.cumulativeAtomic+r;if(n>this.internals.totalCapAtomic)throw new m("cap_exceeded",`cumulative ${n} would exceed totalCap ${this.internals.totalCapAtomic}`);this.sequenceNumber+=1,this.cumulativeAtomic=n;let i={channelId:this.channelId,cumulativeAmount:this.cumulativeAtomic.toString(),sequenceNumber:this.sequenceNumber},s=await this.internals.vault.signWithSession(this.internals.session,i);return this.lastSignedVoucher=s,s}async stream(t,r){if(this.closed)throw new c(this.channelId);let n=await this.signNextVoucher(this.internals.perUnitCapAtomic.toString()),i=Buffer.from(JSON.stringify({payload:n.payload,sessionPublicKey:u(n.sessionPublicKey),sessionRegistration:u(n.sessionRegistration),sessionSignature:u(n.sessionSignature)}),"utf8").toString("base64"),s=new Headers(r?.headers);s.set("X-Tab-Voucher",i),s.set("Accept","text/event-stream");let o=await fetch(t,{...r,headers:s});if(!o.ok){let a=await o.text().catch(()=>"");throw new Error(`tab.stream HTTP ${o.status}: ${a.slice(0,500)}`)}if(!o.body)throw new Error("tab.stream response has no body");return E(o.body)}async close(){if(this.closed)throw new c(this.channelId);let t="";return this.lastSignedVoucher&&this.cumulativeAtomic>0n&&(t=await k(this.internals.facilitatorUrl,this.lastSignedVoucher,this.internals.network)),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:t}}};async function k(e,t,r){let n=`${e.replace(/\/$/,"")}/tab/settle`,i={channelId:t.payload.channelId,cumulativeAmount:t.payload.cumulativeAmount,sequenceNumber:t.payload.sequenceNumber,sessionPublicKey:u(t.sessionPublicKey),sessionSignature:u(t.sessionSignature),sessionRegistration:u(t.sessionRegistration),network:r},s=await fetch(n,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(i)}),o=await s.text();if(!s.ok)throw new Error(`tab settle ${s.status}: ${o.slice(0,500)}`);let a;try{a=JSON.parse(o)}catch{throw new Error(`tab settle returned non-JSON: ${o.slice(0,200)}`)}if(!a.settleTx)throw new Error(`tab settle returned no settleTx: ${o.slice(0,200)}`);return a.settleTx}async function P(e){if(e.network!==e.vault.network)throw new d(`options.network (${e.network}) doesn't match vault.network (${e.vault.network})`);if(e.network!=="solana:mainnet")throw new d(e.network);let t=BigInt(Math.floor(Math.random()*4294967295)),r=new S(e.vault.vaultPda),n=A({vaultPda:r,sellerUrl:e.seller,nonce:BigInt(t)}),i=u(n),s=BigInt(y(e.perUnitCap)),o=BigInt(y(e.totalCap));if(s<=0n)throw new Error("perUnitCap must be > 0");if(o<s)throw new Error("totalCap must be >= perUnitCap");let a=e.sessionDuration??U,l=Math.floor(Date.now()/1e3)+a,h={channelId:i,maxAmountAtomic:o.toString(),expiresAtUnix:l,allowedCounterparty:K(e.seller)},x=await e.vault.authorizeSession(h);return new g({vault:e.vault,network:e.network,seller:e.seller,session:x,channelIdHex:i,channelIdBytes:n,perUnitCapAtomic:s,totalCapAtomic:o,expiresAtUnix:l,facilitatorUrl:e.facilitatorUrl??w})}async function C(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 K(e){if(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(e))try{return new S(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*E(e){let t=e.getReader(),r=new TextDecoder,n="";try{for(;;){let{done:i,value:s}=await t.read();if(i)break;n+=r.decode(s,{stream:!0});let o;for(;(o=n.indexOf(`
|
|
2
2
|
|
|
3
|
-
`))!==-1;){let a=n.slice(0,
|
|
4
|
-
`);yield new TextEncoder().encode(h)}}}}finally{t.releaseLock()}}function
|
|
3
|
+
`))!==-1;){let a=n.slice(0,o);n=n.slice(o+2);let l=V(a);if(l.eventName==="end")return;if(l.data!==null){let h=l.data.replace(/\\n/g,`
|
|
4
|
+
`);yield new TextEncoder().encode(h)}}}}finally{t.releaseLock()}}function V(e){let t=null,r=[];for(let n of e.split(`
|
|
5
5
|
`))n.startsWith("event:")?t=n.slice(6).trim():n.startsWith("data:")&&r.push(n.slice(5).trimStart());return{eventName:t,data:r.length?r.join(`
|
|
6
|
-
`):null}}export{
|
|
6
|
+
`):null}}import{buildRegisterSessionKeyInstruction as $,buildRevokeSessionKeyInstruction as N}from"@dexterai/vault/instructions";import{buildSecp256r1VerifyInstruction as O}from"@dexterai/vault/precompile";import{DEXTER_VAULT_PROGRAM_ID as _,SECP256R1_PROGRAM_ID as M,INSTRUCTIONS_SYSVAR_ID as H}from"@dexterai/vault/constants";export{w as DEFAULT_FACILITATOR_URL,_ as DEXTER_VAULT_PROGRAM_ID,H as INSTRUCTIONS_SYSVAR_ID,M as SECP256R1_PROGRAM_ID,m as SessionScopeExceededError,c as TabClosedError,d as UnsupportedNetworkError,p as atomicToHuman,$ as buildRegisterSessionKeyInstruction,N as buildRevokeSessionKeyInstruction,O as buildSecp256r1VerifyInstruction,I as buildVoucherMessage,y as humanToAtomic,P as openTab,C as resumeTab,T as sessionRegisterMessage,v as sessionRevokeMessage,b as voucherPayloadMessage};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dexterai/x402",
|
|
3
|
-
"version": "3.11.
|
|
3
|
+
"version": "3.11.1",
|
|
4
4
|
"description": "Full-stack x402 SDK - add paid API monetization to any endpoint. Express middleware, React hooks, Access Pass, dynamic pricing. Solana, Base, Polygon, Arbitrum, Optimism, Avalanche, SKALE.",
|
|
5
5
|
"author": "Dexter",
|
|
6
6
|
"license": "MIT",
|