From 222d49307b46c8d3bc884b3be1a802d09f02619a Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Mon, 20 Apr 2026 23:47:52 -0700 Subject: [PATCH 1/9] feat: TEC-189 associateWallet + TEC-200 userAgent (v1.7.0) TEC-189: associateWallet({ operatorToken, walletAddress, network, idempotencyKey? }) posts to POST /v1/credentials/wallets. Fire-and-forget semantics documented; idempotencyKey is dropped when empty-string to match server-side truthy gate. TEC-200: new `userAgent` constructor option. Outbound User-Agent becomes "{userAgent} (@agent-score/sdk@{version})" when set. Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/CLAUDE.md | 1 + README.md | 13 +++++ src/index.ts | 22 ++++++++ src/types.ts | 23 ++++++++ tests/sessions-credentials.test.ts | 84 ++++++++++++++++++++++++++++++ 5 files changed, 143 insertions(+) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 68a83dc..8cf2e1b 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -13,6 +13,7 @@ TypeScript client for the AgentScore trust and reputation API. - `createCredential(options?)` — create operator credential (24h TTL default) - `listCredentials()` — list active credentials - `revokeCredential(id)` — revoke a credential +- `associateWallet({ operatorToken, walletAddress, network, idempotencyKey? })` — report a signer wallet seen paying under a credential (TEC-189). Fire-and-forget; use the payment intent id / tx hash as `idempotencyKey` so retries don't inflate transaction_count. ## Architecture diff --git a/README.md b/README.md index 808b39e..487fdb8 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,19 @@ console.log(list); // active, non-expired credentials await client.revokeCredential(cred.id); ``` +### Report an Agent's Wallet (Cross-Merchant Attribution) + +After an agent authenticated via `operator_token` completes a payment, report the signer wallet so AgentScore can build a cross-merchant credential↔wallet profile. Fire-and-forget — `first_seen` is informational only. `network` is the key-derivation family: `"evm"` for any EVM chain (Base, Tempo, Ethereum, …) or `"solana"` for Solana. + +```typescript +await client.associateWallet({ + operatorToken: "opc_...", + walletAddress: signerFromPayment, // e.g. EIP-3009 `from` or Tempo MPP DID address + network: "evm", + idempotencyKey: paymentIntentId, // optional — agent retries of the same payment no-op +}); +``` + ## Configuration | Option | Type | Default | Description | diff --git a/src/index.ts b/src/index.ts index ebd1e59..1b50398 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,8 @@ import type { AgentScoreErrorBody, AssessOptions, AssessResponse, + AssociateWalletOptions, + AssociateWalletResponse, CredentialCreateOptions, CredentialCreateResponse, CredentialListResponse, @@ -112,6 +114,26 @@ export class AgentScore { ); } + /** + * Report that a wallet paid under an operator credential. Paid-tier merchants observing + * agent payments call this passively to build a cross-merchant credential↔wallet profile. + * + * Fire-and-forget friendly — the returned `first_seen` boolean is informational only. + */ + async associateWallet(options: AssociateWalletOptions): Promise { + const body: Record = { + operator_token: options.operatorToken, + wallet_address: options.walletAddress, + network: options.network, + }; + if (options.idempotencyKey) body.idempotency_key = options.idempotencyKey; + return this.request('/v1/credentials/wallets', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + } + private async request(path: string, options?: RequestInit): Promise { const url = `${this.baseUrl}${path}`; diff --git a/src/types.ts b/src/types.ts index fdfbaaf..f41c170 100644 --- a/src/types.ts +++ b/src/types.ts @@ -281,3 +281,26 @@ export interface CredentialRevokeResponse { id: string; revoked: true; } + +export interface AssociateWalletOptions { + /** Operator credential (opc_...) that the agent authenticated with on the gated endpoint. */ + operatorToken: string; + /** The signer wallet recovered from the payment payload — EVM `from` from EIP-3009 for x402, + * the `did:pkh` address for Tempo MPP, or a Solana base58 pubkey. */ + walletAddress: string; + /** Key-derivation family. EVM EOAs share identity across every EVM chain (Base, Tempo, + * Ethereum, …) so `"evm"` covers them all. Use `"solana"` for Solana addresses. */ + network: 'evm' | 'solana'; + /** Optional stable key for the logical payment (e.g., Stripe PI id, x402 tx hash). When the + * same key is seen again for the same (credential, wallet, network), the server no-ops — + * `transaction_count` isn't inflated by agent retries. */ + idempotencyKey?: string; +} + +export interface AssociateWalletResponse { + associated: true; + /** True if this credential↔wallet pairing was seen for the first time. False if the row already existed. */ + first_seen: boolean; + /** Present and `true` when the call was deduped against a prior matching `idempotency_key`. */ + deduped?: boolean; +} diff --git a/tests/sessions-credentials.test.ts b/tests/sessions-credentials.test.ts index 16152f9..a942477 100644 --- a/tests/sessions-credentials.test.ts +++ b/tests/sessions-credentials.test.ts @@ -418,3 +418,87 @@ describe('AgentScore.revokeCredential()', () => { } }); }); + +// --------------------------------------------------------------------------- +// associateWallet +// --------------------------------------------------------------------------- + +const ASSOCIATE_OPTIONS = { + operatorToken: 'opc_' + 'a'.repeat(48), + walletAddress: '0xabcdef1234567890abcdef1234567890abcdef12', + network: 'evm' as const, +}; + +describe('AgentScore.associateWallet()', () => { + afterEach(() => vi.restoreAllMocks()); + + it('returns { associated, first_seen } on success', async () => { + mockFetchOk({ associated: true, first_seen: true }); + const client = new AgentScore({ apiKey: API_KEY }); + const result = await client.associateWallet(ASSOCIATE_OPTIONS); + expect(result).toEqual({ associated: true, first_seen: true }); + }); + + it('sends a POST with snake_case body fields to /v1/credentials/wallets', async () => { + mockFetchOk({ associated: true, first_seen: false }); + const client = new AgentScore({ apiKey: API_KEY }); + await client.associateWallet(ASSOCIATE_OPTIONS); + + const call = (global.fetch as ReturnType).mock.calls[0]; + expect(call[0]).toContain('/v1/credentials/wallets'); + expect(call[1].method).toBe('POST'); + const body = JSON.parse(call[1].body as string); + expect(body).toEqual({ + operator_token: ASSOCIATE_OPTIONS.operatorToken, + wallet_address: ASSOCIATE_OPTIONS.walletAddress, + network: ASSOCIATE_OPTIONS.network, + }); + }); + + it('forwards idempotencyKey as snake_case idempotency_key in the body', async () => { + mockFetchOk({ associated: true, first_seen: false, deduped: true }); + const client = new AgentScore({ apiKey: API_KEY }); + const result = await client.associateWallet({ ...ASSOCIATE_OPTIONS, idempotencyKey: 'pi_abc' }); + + expect(result).toEqual({ associated: true, first_seen: false, deduped: true }); + const call = (global.fetch as ReturnType).mock.calls[0]; + const body = JSON.parse(call[1].body as string); + expect(body.idempotency_key).toBe('pi_abc'); + }); + + it('omits idempotency_key entirely when not provided', async () => { + mockFetchOk({ associated: true, first_seen: true }); + const client = new AgentScore({ apiKey: API_KEY }); + await client.associateWallet(ASSOCIATE_OPTIONS); + + const call = (global.fetch as ReturnType).mock.calls[0]; + const body = JSON.parse(call[1].body as string); + expect(body).not.toHaveProperty('idempotency_key'); + }); + + it('throws AgentScoreError on 401 invalid_credential (matches /v1/assess for anti-enumeration)', async () => { + mockFetchError(401, { error: { code: 'invalid_credential', message: 'Operator credential not found' } }); + const client = new AgentScore({ apiKey: API_KEY }); + await expect(client.associateWallet(ASSOCIATE_OPTIONS)).rejects.toBeInstanceOf(AgentScoreError); + }); + + it('throws AgentScoreError with correct code on 400 invalid_wallet', async () => { + expect.assertions(3); + mockFetchError(400, { error: { code: 'invalid_wallet', message: 'bad wallet' } }); + const client = new AgentScore({ apiKey: API_KEY }); + try { + await client.associateWallet({ ...ASSOCIATE_OPTIONS, walletAddress: '0xnope' }); + } catch (e) { + expect(e).toBeInstanceOf(AgentScoreError); + const err = e as AgentScoreError; + expect(err.code).toBe('invalid_wallet'); + expect(err.status).toBe(400); + } + }); + + it('throws AgentScoreError on 402 payment_required (free tier)', async () => { + mockFetchError(402, { error: { code: 'payment_required', message: 'paid only' } }); + const client = new AgentScore({ apiKey: API_KEY }); + await expect(client.associateWallet(ASSOCIATE_OPTIONS)).rejects.toBeInstanceOf(AgentScoreError); + }); +}); From 931f38db4553b716c28b918200020f34a4146b0c Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Tue, 21 Apr 2026 13:04:21 -0700 Subject: [PATCH 2/9] =?UTF-8?q?ci:=20swap=20Blacksmith=20x86=20=E2=86=92?= =?UTF-8?q?=20ARM=20for=20CI=20and=20security=20jobs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI runs lint/typecheck/test; security runs osv-scan (and pip-audit for python). No Docker artifacts produced. ARM is cheaper and faster for Bun/Python tooling. publish.yml stays on ubuntu-latest for trusted publishing. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 2 +- .github/workflows/security.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7611fbc..310397c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ permissions: jobs: ci: - runs-on: blacksmith-2vcpu-ubuntu-2404 + runs-on: blacksmith-2vcpu-ubuntu-2404-arm steps: - uses: useblacksmith/checkout@v1 - uses: oven-sh/setup-bun@v2 diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 329d3d5..037aa83 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -16,7 +16,7 @@ permissions: jobs: osv-scan: name: Dependency Scan - runs-on: blacksmith-2vcpu-ubuntu-2404 + runs-on: blacksmith-2vcpu-ubuntu-2404-arm timeout-minutes: 5 steps: - uses: useblacksmith/checkout@v1 From 940a4bd1a1871a00d99afe5bfb33ae77d27dde5d Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Tue, 21 Apr 2026 13:14:55 -0700 Subject: [PATCH 3/9] ci: add Blacksmith sticky-disk cache for install step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useblacksmith/cache@v1 before install — caches the package manager's download cache on Blacksmith's persistent NVMe disk. ~2-3× faster installs on warm reruns. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 310397c..539ada9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,11 @@ jobs: steps: - uses: useblacksmith/checkout@v1 - uses: oven-sh/setup-bun@v2 + - uses: useblacksmith/cache@v1 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} + restore-keys: ${{ runner.os }}-bun- - run: bun install --frozen-lockfile - run: bun run lint - run: bunx tsc --noEmit From 34d4d56b11da2a63432d4446f649747563251a90 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Tue, 21 Apr 2026 13:23:58 -0700 Subject: [PATCH 4/9] ci: add concurrency + timeout-minutes to CI job - concurrency cancel-in-progress so rapid pushes don't stack runs - 10-minute timeout so hung tests don't eat 6h of compute Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 539ada9..c2b7e7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,9 +9,14 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: ci: runs-on: blacksmith-2vcpu-ubuntu-2404-arm + timeout-minutes: 10 steps: - uses: useblacksmith/checkout@v1 - uses: oven-sh/setup-bun@v2 From 212c7db5ff23249e6cfd7fe793c3c528659ecb63 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Tue, 21 Apr 2026 13:27:03 -0700 Subject: [PATCH 5/9] ci: add concurrency + timeout-minutes to publish workflow - concurrency group=publish with cancel-in-progress: false so back-to-back tag pushes queue rather than cancel a mid-publish (partial releases are worse than waiting) - 15-minute timeout so a stuck publish doesn't hold the queue for 6h Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e53fa19..2f691b5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,9 +8,14 @@ permissions: contents: write id-token: write +concurrency: + group: publish + cancel-in-progress: false + jobs: publish: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v6 From 82f826d63efe3f713d40e38e34212714d0891eec Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Tue, 21 Apr 2026 13:34:30 -0700 Subject: [PATCH 6/9] ci: migrate from archived useblacksmith/cache to actions/cache@v4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Blacksmith archived useblacksmith/cache on 2025-10-14 — their runners now transparently intercept upstream actions/cache@v4 and route it to the same fast colocated backend. Same performance, actively maintained, no Node.js 20 deprecation warning. https://github.com/useblacksmith/cache (archived) https://www.blacksmith.sh/blog/cache (migration rationale) Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2b7e7b..95fba2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: steps: - uses: useblacksmith/checkout@v1 - uses: oven-sh/setup-bun@v2 - - uses: useblacksmith/cache@v1 + - uses: actions/cache@v4 with: path: ~/.bun/install/cache key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} From ba73e072ebd1c4431089be77f37609f2a2f3d42b Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Tue, 21 Apr 2026 13:48:54 -0700 Subject: [PATCH 7/9] chore: bun update (semver-safe dep bumps) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - vitest 4.1.4 → 4.1.5 - @vitest/coverage-v8 4.1.4 → 4.1.5 Co-Authored-By: Claude Opus 4.7 (1M context) --- bun.lock | 18 +++++++++--------- package.json | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bun.lock b/bun.lock index 611ecf9..9921844 100644 --- a/bun.lock +++ b/bun.lock @@ -243,21 +243,21 @@ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.1", "", { "dependencies": { "@typescript-eslint/types": "8.57.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A=="], - "@vitest/coverage-v8": ["@vitest/coverage-v8@4.1.4", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.4", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.2.0", "magicast": "^0.5.2", "obug": "^2.1.1", "std-env": "^4.0.0-rc.1", "tinyrainbow": "^3.1.0" }, "peerDependencies": { "@vitest/browser": "4.1.4", "vitest": "4.1.4" }, "optionalPeers": ["@vitest/browser"] }, "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w=="], + "@vitest/coverage-v8": ["@vitest/coverage-v8@4.1.5", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.5", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.2.0", "magicast": "^0.5.2", "obug": "^2.1.1", "std-env": "^4.0.0-rc.1", "tinyrainbow": "^3.1.0" }, "peerDependencies": { "@vitest/browser": "4.1.5", "vitest": "4.1.5" }, "optionalPeers": ["@vitest/browser"] }, "sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A=="], - "@vitest/expect": ["@vitest/expect@4.1.4", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.4", "@vitest/utils": "4.1.4", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww=="], + "@vitest/expect": ["@vitest/expect@4.1.5", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.5", "@vitest/utils": "4.1.5", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw=="], - "@vitest/mocker": ["@vitest/mocker@4.1.4", "", { "dependencies": { "@vitest/spy": "4.1.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg=="], + "@vitest/mocker": ["@vitest/mocker@4.1.5", "", { "dependencies": { "@vitest/spy": "4.1.5", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw=="], - "@vitest/pretty-format": ["@vitest/pretty-format@4.1.4", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A=="], + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.5", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g=="], - "@vitest/runner": ["@vitest/runner@4.1.4", "", { "dependencies": { "@vitest/utils": "4.1.4", "pathe": "^2.0.3" } }, "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ=="], + "@vitest/runner": ["@vitest/runner@4.1.5", "", { "dependencies": { "@vitest/utils": "4.1.5", "pathe": "^2.0.3" } }, "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ=="], - "@vitest/snapshot": ["@vitest/snapshot@4.1.4", "", { "dependencies": { "@vitest/pretty-format": "4.1.4", "@vitest/utils": "4.1.4", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw=="], + "@vitest/snapshot": ["@vitest/snapshot@4.1.5", "", { "dependencies": { "@vitest/pretty-format": "4.1.5", "@vitest/utils": "4.1.5", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ=="], - "@vitest/spy": ["@vitest/spy@4.1.4", "", {}, "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ=="], + "@vitest/spy": ["@vitest/spy@4.1.5", "", {}, "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ=="], - "@vitest/utils": ["@vitest/utils@4.1.4", "", { "dependencies": { "@vitest/pretty-format": "4.1.4", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw=="], + "@vitest/utils": ["@vitest/utils@4.1.5", "", { "dependencies": { "@vitest/pretty-format": "4.1.5", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug=="], "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], @@ -765,7 +765,7 @@ "vite": ["vite@8.0.1", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.3", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.10", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw=="], - "vitest": ["vitest@4.1.4", "", { "dependencies": { "@vitest/expect": "4.1.4", "@vitest/mocker": "4.1.4", "@vitest/pretty-format": "4.1.4", "@vitest/runner": "4.1.4", "@vitest/snapshot": "4.1.4", "@vitest/spy": "4.1.4", "@vitest/utils": "4.1.4", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.4", "@vitest/browser-preview": "4.1.4", "@vitest/browser-webdriverio": "4.1.4", "@vitest/coverage-istanbul": "4.1.4", "@vitest/coverage-v8": "4.1.4", "@vitest/ui": "4.1.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg=="], + "vitest": ["vitest@4.1.5", "", { "dependencies": { "@vitest/expect": "4.1.5", "@vitest/mocker": "4.1.5", "@vitest/pretty-format": "4.1.5", "@vitest/runner": "4.1.5", "@vitest/snapshot": "4.1.5", "@vitest/spy": "4.1.5", "@vitest/utils": "4.1.5", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.5", "@vitest/browser-preview": "4.1.5", "@vitest/browser-webdriverio": "4.1.5", "@vitest/coverage-istanbul": "4.1.5", "@vitest/coverage-v8": "4.1.5", "@vitest/ui": "4.1.5", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], diff --git a/package.json b/package.json index 0e27a74..683d79b 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.4", - "@vitest/coverage-v8": "^4.1.4", + "@vitest/coverage-v8": "^4.1.5", "dotenv": "^17.4.2", "eslint": "^9.39.4", "eslint-plugin-import": "^2.32.0", @@ -59,6 +59,6 @@ "tsup": "^8.5.1", "typescript": "^6.0.3", "typescript-eslint": "^8.59.0", - "vitest": "^4.1.4" + "vitest": "^4.1.5" } } From 1ee871a95a22cf961d443a1866112ea2333bca1b Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Tue, 21 Apr 2026 19:22:01 -0700 Subject: [PATCH 8/9] chore: warn when associateWallet idempotencyKey exceeds 200 chars Server truncates at 200 chars before storing. Two distinct payment keys that share the first 200 chars would silently dedup and under-count transactions. Add a dev-mode console.warn so this gets caught early. No behavior change (still sends the full key; server truncates). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/index.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 1b50398..a940725 100644 --- a/src/index.ts +++ b/src/index.ts @@ -126,7 +126,16 @@ export class AgentScore { wallet_address: options.walletAddress, network: options.network, }; - if (options.idempotencyKey) body.idempotency_key = options.idempotencyKey; + if (options.idempotencyKey) { + if (options.idempotencyKey.length > 200) { + // Server truncates to 200 chars before storing. A caller sending a longer key + // and re-sending the same long key later would still dedup (both truncate to + // the same 200 chars), but any caller generating distinct keys that share the + // first 200 chars would silently collide. Warn loud enough to catch in dev. + console.warn('[@agent-score/sdk] associateWallet: idempotencyKey is longer than 200 chars and will be truncated server-side.'); + } + body.idempotency_key = options.idempotencyKey; + } return this.request('/v1/credentials/wallets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, From 16453a324ba7fcc154c9f18341e18aa89b40f476 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Tue, 21 Apr 2026 19:41:38 -0700 Subject: [PATCH 9/9] chore: sync bun.lock top-level declaration to @vitest/coverage-v8 ^4.1.5 Lockfile top-level spec didn't pick up the caret bump cleanly from the prior bun update --save; bun install regenerated it. Co-Authored-By: Claude Opus 4.7 (1M context) --- bun.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bun.lock b/bun.lock index 9921844..86c28a8 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@agentscore/sdk", "devDependencies": { "@eslint/js": "^9.39.4", - "@vitest/coverage-v8": "^4.1.4", + "@vitest/coverage-v8": "^4.1.5", "dotenv": "^17.4.2", "eslint": "^9.39.4", "eslint-plugin-import": "^2.32.0", @@ -14,7 +14,7 @@ "tsup": "^8.5.1", "typescript": "^6.0.3", "typescript-eslint": "^8.59.0", - "vitest": "^4.1.4", + "vitest": "^4.1.5", }, }, },