Code402.AgentTools.psm1
|
# Code402.AgentTools.psm1 — PowerShell client for the hcrb.in paid agent API. # Origin (canonical): https://hcrb.in — operated by JUANA LIMITED (UK). # # Wire format verified live 2026-09-27 against the production surface: # call: POST {Origin}/v1/tools/{tool}/call body: {"input":{...}} # free: succeeds within daily free quota (per /status) # paid: HTTP 402 + PAYMENT-REQUIRED header (base64 JSON, accepts[]) and/or JSON body # with accepts[], settle.url, "Resend with X-PAYMENT header to settle atomically" # settle: rebuild EIP-3009 transferWithAuthorization, retry with X-PAYMENT header # assets: USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 on eip155:8453 (Base), # EURC 0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42 — quotes list live prices # # secp256k1 (EIP-3009 signing) has no built-in .NET primitive. The module takes a # -SignAuthorization scriptblock so any wallet/backend can supply the signature; # it never sees your private key unless your signer chooses to hold it. Set-StrictMode -Version Latest $script:DefaultOrigin = 'https://hcrb.in' function Get-AgentApiHealth { <# .SYNOPSIS Probes https://hcrb.in/health — free. Returns version, tool catalogue and live prices. #> [CmdletBinding()] param([string]$Origin = $script:DefaultOrigin) Invoke-RestMethod -Uri "$Origin/health" -Method Get } function Get-AgentApiStatus { <# .SYNOPSIS Reads https://hcrb.in/status — free-tier quota and service state. #> [CmdletBinding()] param([string]$Origin = $script:DefaultOrigin) Invoke-RestMethod -Uri "$Origin/status" -Method Get } function Get-AgentToolQuote { <# .SYNOPSIS Probes a paid tool and returns the parsed x402 payment challenge (quote). Does NOT pay. The tool name must exist in the /health catalogue. .EXAMPLE Get-AgentToolQuote -Tool iban-check -ToolInput @{ iban = 'GB29NWBK60161331926819' } #> [CmdletBinding()] param( [Parameter(Mandatory = $true)][string]$Tool, [hashtable]$ToolInput = @{}, [string]$Origin = $script:DefaultOrigin ) $uri = "$Origin/v1/tools/$Tool/call" $bodyObj = @{ input = $ToolInput } try { Invoke-WebRequest -Uri $uri -Method Post -ContentType 'application/json' ` -Body ($bodyObj | ConvertTo-Json -Depth 8 -Compress) | Out-Null throw "Tool '$Tool' answered without payment (free tier). No quote to show." } catch [System.Net.WebException] { $resp = $_.Exception.Response if (-not $resp -or [int]$resp.StatusCode -ne 402) { throw } # Preferred source: PAYMENT-REQUIRED header (base64 JSON, x402 v2 shape). $h = $resp.Headers['PAYMENT-REQUIRED'] if ($h) { try { return ([System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($h)) | ConvertFrom-Json) } catch { } } # Fallback: JSON body (x402 v1 shape with accepts[], settle, amount...). $reader = New-Object System.IO.StreamReader($resp.GetResponseStream()) return ($reader.ReadToEnd() | ConvertFrom-Json) } } function Invoke-AgentApiCall { <# .SYNOPSIS Calls a paid hcrb.in tool end-to-end over x402: request -> 402 quote -> pick asset -> sign EIP-3009 -> retry with X-PAYMENT -> answer + receipt. .PARAMETER SignAuthorization Scriptblock that receives the EIP-3009 authorization fields (@{from,to,value,validAfter,validBefore,nonce}) and returns the 0x-prefixed signature for transferWithAuthorization(from,to,value,validAfter,validBefore,nonce). Keeps the private key in your signer, not in this module. .PARAMETER Currency Which quote asset to accept ('USDC' default, 'EURC' supported live). #> [CmdletBinding()] param( [Parameter(Mandatory = $true)][string]$Tool, [hashtable]$ToolInput = @{}, [Parameter(Mandatory = $true)][scriptblock]$SignAuthorization, [string]$Currency = 'USDC', [string]$Referrer, [string]$Origin = $script:DefaultOrigin ) $uri = "$Origin/v1/tools/$Tool/call" $bodyJson = (@{ input = $ToolInput } | ConvertTo-Json -Depth 8 -Compress) $req = [System.Net.HttpWebRequest]::Create($uri) $req.Method = 'POST'; $req.ContentType = 'application/json' $bytes = [System.Text.Encoding]::UTF8.GetBytes($bodyJson) $req.GetRequestStream().Write($bytes, 0, $bytes.Length) | Out-Null try { $resp = $req.GetResponse() } catch [System.Net.WebException] { $resp = $_.Exception.Response } if (-not $resp) { throw 'No response from origin.' } if ([int]$resp.StatusCode -eq 200) { $reader = New-Object System.IO.StreamReader($resp.GetResponseStream()) return ($reader.ReadToEnd() | ConvertFrom-Json) # free tier answered directly } if ([int]$resp.StatusCode -ne 402) { $reader = New-Object System.IO.StreamReader($resp.GetResponseStream()) throw "Origin returned $([int]$resp.StatusCode): $($reader.ReadToEnd())" } # --- parse the quote --- $quote = $null $h = $resp.Headers['PAYMENT-REQUIRED'] if ($h) { try { $quote = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($h)) | ConvertFrom-Json } catch { } } if (-not $quote) { $reader = New-Object System.IO.StreamReader($resp.GetResponseStream()) $quote = $reader.ReadToEnd() | ConvertFrom-Json } $accept = @($quote.accepts | Where-Object { $_.extra.currency -eq $Currency })[0] if (-not $accept) { throw "No $Currency quote offered. Offered: $(@($quote.accepts | ForEach-Object { $_.extra.currency }) -join ', ')" } # --- build EIP-3009 authorization --- $now = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() $auth = @{ from = $null # <- your wallet address; the signer block may fill it to = $accept.payTo value = $accept.maxAmountRequired validAfter = $now - 60 validBefore = $now + [Math]::Min(300, [int]$accept.maxTimeoutSeconds) nonce = '0x' + ([Guid]::NewGuid().ToString('N')) } $signature = & $SignAuthorization $auth if ($signature -notmatch '^0x[0-9a-fA-F]{130}$') { throw 'Signer must return a 0x-prefixed 65-byte r||s||v signature.' } # --- settle: resend identical request with X-PAYMENT (x402 v1 payload) --- $payment = @{ x402Version = 1 payload = @{ authorization = $auth; signature = $signature } accepted = $accept } | ConvertTo-Json -Depth 8 -Compress $xPayment = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($payment)) $payReq = [System.Net.HttpWebRequest]::Create($uri) $payReq.Method = 'POST'; $payReq.ContentType = 'application/json' $payReq.Headers['X-PAYMENT'] = $xPayment if ($Referrer) { $payReq.Headers['x-referrer'] = $Referrer } # referral attribution (hcrb.in/referrals) $payReq.GetRequestStream().Write($bytes, 0, $bytes.Length) | Out-Null try { $payResp = $payReq.GetResponse() } catch [System.Net.WebException] { $payResp = $_.Exception.Response } if (-not $payResp) { throw 'No response on settle retry.' } $payReader = New-Object System.IO.StreamReader($payResp.GetResponseStream()) $result = $payReader.ReadToEnd() | ConvertFrom-Json if ([int]$payResp.StatusCode -ne 200) { throw "Settle failed $([int]$payResp.StatusCode): $($result | ConvertTo-Json -Compress -Depth 4)" } # Receipt: PAYMENT-RESPONSE header when present; XDR-1 receipt body fields otherwise. $receiptHeader = $payResp.Headers['PAYMENT-RESPONSE'] [pscustomobject]@{ Tool = $Tool Result = $result Paid = $accept.maxAmountRequired Currency = $Currency PayTo = $accept.payTo Referrer = $Referrer TxReceipt = $result.receipt RawReceiptHeader = $receiptHeader } } function Test-AgentApiReceipt { <# .SYNOPSIS Verifies a signed XDR-1 receipt via the free verifier (POST /v1/receipt/verify — free forever, no quota, any issuer). .PARAMETER Receipt The receipt object (or JSON string) returned by a tool call. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)]$Receipt, [string]$Origin = $script:DefaultOrigin ) if ($Receipt -is [string]) { $receiptObj = $Receipt | ConvertFrom-Json } else { $receiptObj = $Receipt } Invoke-RestMethod -Uri "$Origin/v1/receipt/verify" -Method Post -ContentType 'application/json' ` -Body (@{ receipt = $receiptObj } | ConvertTo-Json -Depth 12 -Compress) } # --- Code402-prefixed aliases (adapter naming from the distribution plan) --- New-Alias Get-Code402Manifest Get-AgentApiHealth New-Alias Test-Code402Connection Get-AgentApiStatus New-Alias Invoke-Code402Tool Invoke-AgentApiCall New-Alias Verify-Code402Receipt Test-AgentApiReceipt Export-ModuleMember -Function Get-AgentApiHealth, Get-AgentApiStatus, Get-AgentToolQuote, Invoke-AgentApiCall, Test-AgentApiReceipt ` -Alias Get-Code402Manifest, Test-Code402Connection, Invoke-Code402Tool, Verify-Code402Receipt |