@buildersgarden/siwa 0.0.11 → 0.0.13
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 +2 -2
- package/dist/erc8128.d.ts +38 -10
- package/dist/erc8128.js +51 -19
- package/dist/express.d.ts +3 -0
- package/dist/express.js +1 -0
- package/dist/identity.d.ts +7 -7
- package/dist/identity.js +8 -8
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/keystore.d.ts +17 -57
- package/dist/keystore.js +20 -54
- package/dist/next.d.ts +3 -0
- package/dist/next.js +1 -0
- package/dist/receipt.d.ts +2 -0
- package/dist/registry.d.ts +22 -3
- package/dist/registry.js +20 -9
- package/dist/signer.d.ts +145 -0
- package/dist/signer.js +179 -0
- package/dist/siwa.d.ts +33 -8
- package/dist/siwa.js +53 -40
- package/dist/tba.d.ts +82 -0
- package/dist/tba.js +113 -0
- package/package.json +9 -1
package/dist/siwa.js
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* Dependencies:
|
|
8
8
|
* npm install viem
|
|
9
9
|
*/
|
|
10
|
-
import { verifyMessage, hashMessage, } from 'viem';
|
|
11
10
|
import * as crypto from 'crypto';
|
|
12
11
|
import { getAgent, getReputation } from './registry.js';
|
|
13
12
|
// ─── Types ───────────────────────────────────────────────────────────
|
|
@@ -39,11 +38,12 @@ export function buildSIWAResponse(result) {
|
|
|
39
38
|
agentRegistry: result.agentRegistry || undefined,
|
|
40
39
|
chainId: result.chainId || undefined,
|
|
41
40
|
verified: result.verified,
|
|
41
|
+
...(result.signerType ? { signerType: result.signerType } : {}),
|
|
42
42
|
};
|
|
43
43
|
const skillRef = {
|
|
44
44
|
name: '@buildersgarden/siwa',
|
|
45
45
|
install: 'npm install @buildersgarden/siwa',
|
|
46
|
-
url: 'https://siwa.
|
|
46
|
+
url: 'https://siwa.id/skill.md',
|
|
47
47
|
};
|
|
48
48
|
if (result.valid) {
|
|
49
49
|
return { status: 'authenticated', ...base };
|
|
@@ -284,39 +284,55 @@ export async function createSIWANonce(params, client, options) {
|
|
|
284
284
|
return result;
|
|
285
285
|
}
|
|
286
286
|
/**
|
|
287
|
-
* Sign a SIWA message using the
|
|
287
|
+
* Sign a SIWA message using the provided signer.
|
|
288
288
|
*
|
|
289
|
-
* The
|
|
290
|
-
*
|
|
289
|
+
* The signer abstracts the wallet implementation, allowing you to use:
|
|
290
|
+
* - createKeyringProxySigner(config) — Keyring proxy server
|
|
291
|
+
* - createLocalAccountSigner(account) — viem LocalAccount (private key)
|
|
292
|
+
* - createWalletClientSigner(client) — viem WalletClient (Privy, MetaMask, etc.)
|
|
291
293
|
*
|
|
292
|
-
* The agent address is always resolved from the
|
|
294
|
+
* The agent address is always resolved from the signer — the single source
|
|
293
295
|
* of truth — so the caller doesn't need to supply (or risk hallucinating) it.
|
|
294
|
-
* If `fields.address` is provided it must match the
|
|
296
|
+
* If `fields.address` is provided it must match the signer's address.
|
|
295
297
|
*
|
|
296
298
|
* @param fields — SIWA message fields (domain, agentId, etc.). `address` is optional.
|
|
297
|
-
* @param
|
|
299
|
+
* @param signer — A Signer implementation (see createKeyringProxySigner, createLocalAccountSigner, createWalletClientSigner)
|
|
298
300
|
* @returns { message, signature, address } — the plaintext message, EIP-191 signature, and resolved address
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* ```typescript
|
|
304
|
+
* import { signSIWAMessage, createLocalAccountSigner } from '@buildersgarden/siwa';
|
|
305
|
+
* import { privateKeyToAccount } from 'viem/accounts';
|
|
306
|
+
*
|
|
307
|
+
* const account = privateKeyToAccount('0x...');
|
|
308
|
+
* const signer = createLocalAccountSigner(account);
|
|
309
|
+
*
|
|
310
|
+
* const { message, signature, address } = await signSIWAMessage({
|
|
311
|
+
* domain: 'example.com',
|
|
312
|
+
* uri: 'https://example.com/login',
|
|
313
|
+
* agentId: 123,
|
|
314
|
+
* agentRegistry: 'eip155:84532:0x...',
|
|
315
|
+
* chainId: 84532,
|
|
316
|
+
* nonce: 'abc123',
|
|
317
|
+
* issuedAt: new Date().toISOString(),
|
|
318
|
+
* }, signer);
|
|
319
|
+
* ```
|
|
299
320
|
*/
|
|
300
|
-
export async function signSIWAMessage(fields,
|
|
301
|
-
//
|
|
302
|
-
const
|
|
303
|
-
// Resolve the address from the keystore — the trusted source of truth
|
|
304
|
-
const keystoreAddress = await getAddress(keystoreConfig);
|
|
305
|
-
if (!keystoreAddress) {
|
|
306
|
-
throw new Error('No wallet found in keystore. Run createWallet() first.');
|
|
307
|
-
}
|
|
321
|
+
export async function signSIWAMessage(fields, signer) {
|
|
322
|
+
// Resolve the address from the signer — the trusted source of truth
|
|
323
|
+
const signerAddress = await signer.getAddress();
|
|
308
324
|
// If the caller supplied an address, verify it matches (defensive check)
|
|
309
|
-
if (fields.address &&
|
|
310
|
-
throw new Error(`Address mismatch:
|
|
325
|
+
if (fields.address && signerAddress.toLowerCase() !== fields.address.toLowerCase()) {
|
|
326
|
+
throw new Error(`Address mismatch: signer has ${signerAddress}, message claims ${fields.address}`);
|
|
311
327
|
}
|
|
312
328
|
const resolvedFields = {
|
|
313
329
|
...fields,
|
|
314
|
-
address:
|
|
330
|
+
address: signerAddress,
|
|
315
331
|
};
|
|
316
332
|
const message = buildSIWAMessage(resolvedFields);
|
|
317
|
-
// Sign via
|
|
318
|
-
const
|
|
319
|
-
return { message, signature
|
|
333
|
+
// Sign via signer
|
|
334
|
+
const signature = await signer.signMessage(message);
|
|
335
|
+
return { message, signature, address: signerAddress };
|
|
320
336
|
}
|
|
321
337
|
/**
|
|
322
338
|
* Verify a SIWA message + signature.
|
|
@@ -342,8 +358,12 @@ export async function verifySIWA(message, signature, expectedDomain, nonceValid,
|
|
|
342
358
|
try {
|
|
343
359
|
// 1. Parse
|
|
344
360
|
const fields = parseSIWAMessage(message);
|
|
345
|
-
// 2.
|
|
346
|
-
|
|
361
|
+
// 2. Verify signature (supports both EOA and ERC-1271 smart wallets)
|
|
362
|
+
// Using client.verifyMessage handles:
|
|
363
|
+
// - EOA signatures (ECDSA recovery)
|
|
364
|
+
// - ERC-1271 smart contract wallets (Safe, Argent, etc.)
|
|
365
|
+
// - ERC-6492 pre-deployed smart wallets
|
|
366
|
+
const isValid = await client.verifyMessage({
|
|
347
367
|
address: fields.address,
|
|
348
368
|
message,
|
|
349
369
|
signature: signature,
|
|
@@ -352,6 +372,9 @@ export async function verifySIWA(message, signature, expectedDomain, nonceValid,
|
|
|
352
372
|
return fail(fields, SIWAErrorCode.INVALID_SIGNATURE, 'Invalid signature');
|
|
353
373
|
}
|
|
354
374
|
const recovered = fields.address;
|
|
375
|
+
// 2b. Detect signer type (EOA vs smart contract account)
|
|
376
|
+
const signerCode = await client.getCode({ address: fields.address });
|
|
377
|
+
const signerType = (signerCode && signerCode !== '0x') ? 'sca' : 'eoa';
|
|
355
378
|
// 3. Address match is implicit in verifyMessage (it checks against the address)
|
|
356
379
|
// 4. Domain binding
|
|
357
380
|
if (fields.domain !== expectedDomain) {
|
|
@@ -404,22 +427,7 @@ export async function verifySIWA(message, signature, expectedDomain, nonceValid,
|
|
|
404
427
|
return fail(fields, SIWAErrorCode.NOT_REGISTERED, 'Agent is not registered on the ERC-8004 Identity Registry');
|
|
405
428
|
}
|
|
406
429
|
if (owner.toLowerCase() !== recovered.toLowerCase()) {
|
|
407
|
-
|
|
408
|
-
const messageHash = hashMessage(message);
|
|
409
|
-
try {
|
|
410
|
-
const magicValue = await client.readContract({
|
|
411
|
-
address: owner,
|
|
412
|
-
abi: [{ name: 'isValidSignature', type: 'function', stateMutability: 'view', inputs: [{ name: 'hash', type: 'bytes32' }, { name: 'signature', type: 'bytes' }], outputs: [{ name: '', type: 'bytes4' }] }],
|
|
413
|
-
functionName: 'isValidSignature',
|
|
414
|
-
args: [messageHash, signature],
|
|
415
|
-
});
|
|
416
|
-
if (magicValue !== '0x1626ba7e') {
|
|
417
|
-
return fail(fields, SIWAErrorCode.NOT_OWNER, 'Signer is not the owner of this agent NFT (ERC-1271 check also failed)');
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
catch {
|
|
421
|
-
return fail(fields, SIWAErrorCode.NOT_OWNER, 'Signer is not the owner of this agent NFT');
|
|
422
|
-
}
|
|
430
|
+
return fail(fields, SIWAErrorCode.NOT_OWNER, 'Signer is not the owner of this agent NFT');
|
|
423
431
|
}
|
|
424
432
|
// 8. Base result
|
|
425
433
|
const baseResult = {
|
|
@@ -429,9 +437,14 @@ export async function verifySIWA(message, signature, expectedDomain, nonceValid,
|
|
|
429
437
|
agentRegistry: fields.agentRegistry,
|
|
430
438
|
chainId: fields.chainId,
|
|
431
439
|
verified: 'onchain',
|
|
440
|
+
signerType,
|
|
432
441
|
};
|
|
433
442
|
if (!criteria)
|
|
434
443
|
return baseResult;
|
|
444
|
+
// Signer type policy (checked before fetching metadata for early exit)
|
|
445
|
+
if (criteria.allowedSignerTypes?.length && !criteria.allowedSignerTypes.includes(signerType)) {
|
|
446
|
+
return { ...baseResult, valid: false, code: SIWAErrorCode.CUSTOM_CHECK_FAILED, error: `Signer type '${signerType}' is not in allowed types [${criteria.allowedSignerTypes.join(', ')}]` };
|
|
447
|
+
}
|
|
435
448
|
const agent = await getAgent(fields.agentId, {
|
|
436
449
|
registryAddress: registryAddress,
|
|
437
450
|
client,
|
package/dist/tba.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tba.ts
|
|
3
|
+
*
|
|
4
|
+
* ERC-6551 Token Bound Account address computation utilities.
|
|
5
|
+
*
|
|
6
|
+
* Pure math — no RPC calls. Useful for platforms that want to verify
|
|
7
|
+
* a signer is specifically a TBA derived from a given agent NFT.
|
|
8
|
+
*
|
|
9
|
+
* The ERC-6551 registry deploys a modified ERC-1167 minimal proxy with
|
|
10
|
+
* immutable data (salt, chainId, tokenContract, tokenId) appended.
|
|
11
|
+
* The CREATE2 address is deterministic from these inputs.
|
|
12
|
+
*/
|
|
13
|
+
import { type Address, type Hex } from 'viem';
|
|
14
|
+
/** Canonical ERC-6551 registry address, deployed across all EVM chains. */
|
|
15
|
+
export declare const ERC6551_REGISTRY: "0x000000006551c19487814612e58FE06813775758";
|
|
16
|
+
/**
|
|
17
|
+
* Compute the deterministic ERC-6551 Token Bound Account address for an NFT.
|
|
18
|
+
*
|
|
19
|
+
* Pure math — no RPC call needed. Given the same inputs, the address is
|
|
20
|
+
* the same whether or not the account is deployed.
|
|
21
|
+
*
|
|
22
|
+
* Mirrors the on-chain `ERC6551Registry.account()` function exactly.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* import { computeTbaAddress } from '@buildersgarden/siwa/tba';
|
|
27
|
+
*
|
|
28
|
+
* const tba = computeTbaAddress({
|
|
29
|
+
* implementation: '0x...TBAImpl',
|
|
30
|
+
* tokenContract: '0x...AgentRegistry',
|
|
31
|
+
* tokenId: 42n,
|
|
32
|
+
* chainId: 84532,
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function computeTbaAddress(params: {
|
|
37
|
+
/** TBA implementation contract address. */
|
|
38
|
+
implementation: Address;
|
|
39
|
+
/** NFT contract address. */
|
|
40
|
+
tokenContract: Address;
|
|
41
|
+
/** NFT token ID. */
|
|
42
|
+
tokenId: bigint;
|
|
43
|
+
/** Chain ID where the NFT lives. */
|
|
44
|
+
chainId: number;
|
|
45
|
+
/** Registry address (defaults to canonical ERC-6551 registry). */
|
|
46
|
+
registry?: Address;
|
|
47
|
+
/** CREATE2 salt (defaults to bytes32(0)). */
|
|
48
|
+
salt?: Hex;
|
|
49
|
+
}): Address;
|
|
50
|
+
/**
|
|
51
|
+
* Check if a signer address matches the expected TBA for an agent NFT.
|
|
52
|
+
*
|
|
53
|
+
* Useful for platforms that want to verify the signer is specifically
|
|
54
|
+
* a TBA derived from the agent's registry, not just any smart contract.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* import { isTbaForAgent } from '@buildersgarden/siwa/tba';
|
|
59
|
+
*
|
|
60
|
+
* const isValid = isTbaForAgent({
|
|
61
|
+
* signerAddress: agent.address as Address,
|
|
62
|
+
* implementation: '0x...TBAImpl',
|
|
63
|
+
* agentRegistry: '0x...AgentRegistry',
|
|
64
|
+
* agentId: 42,
|
|
65
|
+
* chainId: 84532,
|
|
66
|
+
* });
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare function isTbaForAgent(params: {
|
|
70
|
+
/** The signer address to check. */
|
|
71
|
+
signerAddress: Address;
|
|
72
|
+
/** TBA implementation contract address. */
|
|
73
|
+
implementation: Address;
|
|
74
|
+
/** ERC-8004 agent registry address. */
|
|
75
|
+
agentRegistry: Address;
|
|
76
|
+
/** Agent NFT token ID. */
|
|
77
|
+
agentId: number;
|
|
78
|
+
/** Chain ID. */
|
|
79
|
+
chainId: number;
|
|
80
|
+
/** CREATE2 salt (defaults to bytes32(0)). */
|
|
81
|
+
salt?: Hex;
|
|
82
|
+
}): boolean;
|
package/dist/tba.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tba.ts
|
|
3
|
+
*
|
|
4
|
+
* ERC-6551 Token Bound Account address computation utilities.
|
|
5
|
+
*
|
|
6
|
+
* Pure math — no RPC calls. Useful for platforms that want to verify
|
|
7
|
+
* a signer is specifically a TBA derived from a given agent NFT.
|
|
8
|
+
*
|
|
9
|
+
* The ERC-6551 registry deploys a modified ERC-1167 minimal proxy with
|
|
10
|
+
* immutable data (salt, chainId, tokenContract, tokenId) appended.
|
|
11
|
+
* The CREATE2 address is deterministic from these inputs.
|
|
12
|
+
*/
|
|
13
|
+
import { encodePacked, getContractAddress, } from 'viem';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Constants
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
/** Canonical ERC-6551 registry address, deployed across all EVM chains. */
|
|
18
|
+
export const ERC6551_REGISTRY = '0x000000006551c19487814612e58FE06813775758';
|
|
19
|
+
/**
|
|
20
|
+
* ERC-1167 modified proxy init code prefix + proxy runtime header (20 bytes).
|
|
21
|
+
*
|
|
22
|
+
* Breakdown:
|
|
23
|
+
* 3d60ad80600a3d3981f3 — init code (10 bytes): deploys 0xad bytes of runtime
|
|
24
|
+
* 363d3d373d3d3d363d73 — proxy runtime header (10 bytes): delegatecall setup
|
|
25
|
+
*/
|
|
26
|
+
const ERC1167_HEADER = '0x3d60ad80600a3d3981f3363d3d373d3d3d363d73';
|
|
27
|
+
/** ERC-1167 proxy runtime footer (15 bytes): delegatecall + return/revert. */
|
|
28
|
+
const ERC1167_FOOTER = '0x5af43d82803e903d91602b57fd5bf3';
|
|
29
|
+
/** Default salt (bytes32 zero). */
|
|
30
|
+
const DEFAULT_SALT = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Address computation
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
/**
|
|
35
|
+
* Compute the deterministic ERC-6551 Token Bound Account address for an NFT.
|
|
36
|
+
*
|
|
37
|
+
* Pure math — no RPC call needed. Given the same inputs, the address is
|
|
38
|
+
* the same whether or not the account is deployed.
|
|
39
|
+
*
|
|
40
|
+
* Mirrors the on-chain `ERC6551Registry.account()` function exactly.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* import { computeTbaAddress } from '@buildersgarden/siwa/tba';
|
|
45
|
+
*
|
|
46
|
+
* const tba = computeTbaAddress({
|
|
47
|
+
* implementation: '0x...TBAImpl',
|
|
48
|
+
* tokenContract: '0x...AgentRegistry',
|
|
49
|
+
* tokenId: 42n,
|
|
50
|
+
* chainId: 84532,
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function computeTbaAddress(params) {
|
|
55
|
+
const { implementation, tokenContract, tokenId, chainId, registry = ERC6551_REGISTRY, salt = DEFAULT_SALT, } = params;
|
|
56
|
+
// Build the 183-byte creation code (init code) matching ERC6551Registry.sol
|
|
57
|
+
//
|
|
58
|
+
// Layout:
|
|
59
|
+
// [20 bytes] ERC1167_HEADER (init prefix + proxy header)
|
|
60
|
+
// [20 bytes] implementation address
|
|
61
|
+
// [15 bytes] ERC1167_FOOTER (proxy footer)
|
|
62
|
+
// [32 bytes] salt
|
|
63
|
+
// [32 bytes] chainId
|
|
64
|
+
// [32 bytes] tokenContract (address as uint256, left-padded)
|
|
65
|
+
// [32 bytes] tokenId
|
|
66
|
+
const creationCode = encodePacked(['bytes', 'address', 'bytes', 'bytes32', 'uint256', 'uint256', 'uint256'], [
|
|
67
|
+
ERC1167_HEADER,
|
|
68
|
+
implementation,
|
|
69
|
+
ERC1167_FOOTER,
|
|
70
|
+
salt,
|
|
71
|
+
BigInt(chainId),
|
|
72
|
+
BigInt(tokenContract),
|
|
73
|
+
tokenId,
|
|
74
|
+
]);
|
|
75
|
+
return getContractAddress({
|
|
76
|
+
bytecode: creationCode,
|
|
77
|
+
from: registry,
|
|
78
|
+
opcode: 'CREATE2',
|
|
79
|
+
salt,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Agent-specific helpers
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
/**
|
|
86
|
+
* Check if a signer address matches the expected TBA for an agent NFT.
|
|
87
|
+
*
|
|
88
|
+
* Useful for platforms that want to verify the signer is specifically
|
|
89
|
+
* a TBA derived from the agent's registry, not just any smart contract.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* import { isTbaForAgent } from '@buildersgarden/siwa/tba';
|
|
94
|
+
*
|
|
95
|
+
* const isValid = isTbaForAgent({
|
|
96
|
+
* signerAddress: agent.address as Address,
|
|
97
|
+
* implementation: '0x...TBAImpl',
|
|
98
|
+
* agentRegistry: '0x...AgentRegistry',
|
|
99
|
+
* agentId: 42,
|
|
100
|
+
* chainId: 84532,
|
|
101
|
+
* });
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function isTbaForAgent(params) {
|
|
105
|
+
const expected = computeTbaAddress({
|
|
106
|
+
implementation: params.implementation,
|
|
107
|
+
tokenContract: params.agentRegistry,
|
|
108
|
+
tokenId: BigInt(params.agentId),
|
|
109
|
+
chainId: params.chainId,
|
|
110
|
+
salt: params.salt,
|
|
111
|
+
});
|
|
112
|
+
return expected.toLowerCase() === params.signerAddress.toLowerCase();
|
|
113
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buildersgarden/siwa",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"default": "./dist/index.js"
|
|
9
9
|
},
|
|
10
|
+
"./signer": {
|
|
11
|
+
"types": "./dist/signer.d.ts",
|
|
12
|
+
"default": "./dist/signer.js"
|
|
13
|
+
},
|
|
10
14
|
"./keystore": {
|
|
11
15
|
"types": "./dist/keystore.d.ts",
|
|
12
16
|
"default": "./dist/keystore.js"
|
|
@@ -46,6 +50,10 @@
|
|
|
46
50
|
"./express": {
|
|
47
51
|
"types": "./dist/express.d.ts",
|
|
48
52
|
"default": "./dist/express.js"
|
|
53
|
+
},
|
|
54
|
+
"./tba": {
|
|
55
|
+
"types": "./dist/tba.d.ts",
|
|
56
|
+
"default": "./dist/tba.js"
|
|
49
57
|
}
|
|
50
58
|
},
|
|
51
59
|
"main": "./dist/index.js",
|