diff --git a/scripts/identity-utils/README.md b/scripts/identity-utils/README.md new file mode 100644 index 0000000..405dfa1 --- /dev/null +++ b/scripts/identity-utils/README.md @@ -0,0 +1,86 @@ +# Identity Binding Utilities + +Command-line scripts for managing didgit.dev identity bindings on Base Sepolia. + +## Prerequisites + +```bash +npm install viem +export WALLET_PRIVATE_KEY=0x... +``` + +## Scripts + +### bind-identity.mjs + +Binds an existing EAS attestation to the UsernameUniqueResolver contract. + +**When to use:** After creating an identity attestation via the web app or API, run this to register it in the resolver. This enables contribution attestations and automatically sets the `*/*` repository pattern. + +```bash +node bind-identity.mjs +``` + +**What it does:** +1. Calls `bindIdentity(attestationUid, wallet, domain, username)` on the resolver +2. Automatically registers the `*/*` pattern (all repos in all orgs) +3. Verifies the binding was successful + +### verify-binding.mjs + +Checks if an identity is bound in the resolver. + +```bash +node verify-binding.mjs +``` + +**Output:** +- Identity owner (wallet address for username) +- Binding status + +### check-my-patterns.mjs + +Lists all repository patterns registered for your identity. + +```bash +node check-my-patterns.mjs +``` + +**Output:** +- All registered patterns (e.g., `*/*`, `cyberstorm-dev/*`, etc.) +- Enabled/disabled status for each pattern + +## Architecture Notes + +### Why Two Steps? + +1. **EAS Attestation** - Creates the cryptographic proof (signature + gist) +2. **Resolver Binding** - Registers the attestation on-chain for uniqueness enforcement + +The resolver ensures: +- One username → one wallet (no squatting) +- One wallet → one username (no multi-claiming) +- Repository patterns are tracked per identity + +### Automatic `*/*` Registration + +When `bindIdentity()` is called, the contract automatically enables the `*/*` pattern: + +```solidity +bytes32 wildcardPatternKey = _patternKey("*", "*"); +repoPatterns[identityKey][wildcardPatternKey] = true; +``` + +This allows immediate contribution attestations to any repo without manual pattern setup. + +## Contract Addresses + +- **Resolver:** `0x20c1cb4313efc28d325d3a893a68ca8c82911b0c` +- **EAS:** `0x4200000000000000000000000000000000000021` +- **Network:** Base Sepolia + +## Related Docs + +- [Identity Schema](../../docs/schemas/IDENTITY.md) +- [Contribution Schema](../../docs/schemas/CONTRIBUTION.md) +- [Contract Reference](../../docs/reference/CONTRACTS.md) diff --git a/scripts/identity-utils/bind-identity.mjs b/scripts/identity-utils/bind-identity.mjs new file mode 100644 index 0000000..424001f --- /dev/null +++ b/scripts/identity-utils/bind-identity.mjs @@ -0,0 +1,133 @@ +#!/usr/bin/env node +/** + * Bind Loki's identity in the resolver + * This enables contribution attestations and automatically sets wildcard pattern + */ + +import { createWalletClient, http, createPublicClient } from 'viem'; +import { baseSepolia } from 'viem/chains'; +import { privateKeyToAccount } from 'viem/accounts'; + +const RESOLVER_ADDRESS = '0x20c1cb4313efc28d325d3a893a68ca8c82911b0c'; +const WALLET_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY; +const RPC_URL = 'https://sepolia.base.org'; + +// My attestation UID from IDENTITY.md +const MY_ATTESTATION_UID = '0xd440aad8b6751a2e1e0d2045a0443e615fec882f92313b793b682f2b546cb109'; +const MY_WALLET = '0x7a1de0Fa7242194bbA84E915f39bF7E621B50d2E'; +const MY_USERNAME = 'loki-cyberstorm'; + +const RESOLVER_ABI = [ + { + type: 'function', + name: 'bindIdentity', + inputs: [ + { name: 'attestationUid', type: 'bytes32' }, + { name: 'recipient', type: 'address' }, + { name: 'domain', type: 'string' }, + { name: 'username', type: 'string' } + ], + outputs: [], + stateMutability: 'nonpayable' + }, + { + type: 'function', + name: 'getIdentity', + inputs: [ + { name: 'domain', type: 'string' }, + { name: 'username', type: 'string' } + ], + outputs: [{ type: 'address' }], + stateMutability: 'view' + } +]; + +async function main() { + if (!WALLET_PRIVATE_KEY) { + console.error('āŒ WALLET_PRIVATE_KEY not set in environment'); + process.exit(1); + } + + const account = privateKeyToAccount(WALLET_PRIVATE_KEY); + console.log(`šŸŽ­ Binding identity for: ${account.address}\n`); + + const walletClient = createWalletClient({ + account, + chain: baseSepolia, + transport: http(RPC_URL) + }); + + const publicClient = createPublicClient({ + chain: baseSepolia, + transport: http(RPC_URL) + }); + + console.log('šŸ“‹ Identity Details:'); + console.log(` Username: github.com:${MY_USERNAME}`); + console.log(` Wallet: ${MY_WALLET}`); + console.log(` Attestation UID: ${MY_ATTESTATION_UID}`); + console.log(); + + try { + console.log('šŸ“ Binding identity in resolver...'); + + const hash = await walletClient.writeContract({ + address: RESOLVER_ADDRESS, + abi: RESOLVER_ABI, + functionName: 'bindIdentity', + args: [ + MY_ATTESTATION_UID, + MY_WALLET, + 'github.com', + MY_USERNAME + ] + }); + + console.log(`šŸ“¤ Transaction sent: ${hash}`); + console.log(`šŸ”— Explorer: https://sepolia.basescan.org/tx/${hash}`); + + // Wait for confirmation + const receipt = await publicClient.waitForTransactionReceipt({ hash }); + + if (receipt.status === 'success') { + console.log('āœ… Identity bound successfully!'); + } else { + console.log('āŒ Transaction failed'); + process.exit(1); + } + + // Verify binding + console.log('\nšŸ” Verifying binding...'); + + const boundWallet = await publicClient.readContract({ + address: RESOLVER_ADDRESS, + abi: RESOLVER_ABI, + functionName: 'getIdentity', + args: ['github.com', MY_USERNAME] + }); + + console.log(` github.com:${MY_USERNAME} -> ${boundWallet}`); + console.log(` Match: ${boundWallet.toLowerCase() === MY_WALLET.toLowerCase() ? 'āœ…' : 'āŒ'}`); + + console.log('\nšŸŽ‰ Done! The */* repository pattern was automatically enabled.'); + console.log('šŸš€ Loki can now attest contributions to any GitHub repo in any org.'); + + } catch (error) { + console.error('āŒ Error:', error.message); + if (error.message.includes('IDENTITY_TAKEN')) { + console.log('\nšŸ’” Identity is already bound. Checking current binding...'); + try { + const boundWallet = await publicClient.readContract({ + address: RESOLVER_ADDRESS, + abi: RESOLVER_ABI, + functionName: 'getIdentity', + args: ['github.com', MY_USERNAME] + }); + console.log(` Current binding: github.com:${MY_USERNAME} -> ${boundWallet}`); + } catch {} + } + process.exit(1); + } +} + +main(); diff --git a/scripts/identity-utils/check-my-patterns.mjs b/scripts/identity-utils/check-my-patterns.mjs new file mode 100644 index 0000000..cbf5201 --- /dev/null +++ b/scripts/identity-utils/check-my-patterns.mjs @@ -0,0 +1,73 @@ +#!/usr/bin/env node +import { createWalletClient, http, createPublicClient } from 'viem'; +import { baseSepolia } from 'viem/chains'; +import { privateKeyToAccount } from 'viem/accounts'; + +const RESOLVER_ADDRESS = '0x20c1cb4313efc28d325d3a893a68ca8c82911b0c'; +const WALLET_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY; +const RPC_URL = 'https://sepolia.base.org'; + +const RESOLVER_ABI = [ + { + type: 'function', + name: 'getRepositoryPatterns', + inputs: [ + { name: 'domain', type: 'string' }, + { name: 'identifier', type: 'string' } + ], + outputs: [{ + name: 'patterns', + type: 'tuple[]', + components: [ + { name: 'namespace', type: 'string' }, + { name: 'name', type: 'string' }, + { name: 'enabled', type: 'bool' } + ] + }], + stateMutability: 'view' + } +]; + +async function main() { + const account = privateKeyToAccount(WALLET_PRIVATE_KEY); + + const client = createPublicClient({ + account, + chain: baseSepolia, + transport: http(RPC_URL) + }); + + console.log('šŸ” Checking repository patterns for loki-cyberstorm...\n'); + + try { + const patterns = await client.readContract({ + address: RESOLVER_ADDRESS, + abi: RESOLVER_ABI, + functionName: 'getRepositoryPatterns', + args: ['github.com', 'loki-cyberstorm'], + account + }); + + if (patterns.length === 0) { + console.log('āŒ No patterns registered'); + } else { + console.log(`āœ… Registered patterns (${patterns.length}):`); + patterns.forEach(p => { + const status = p.enabled ? 'āœ…' : 'āŒ'; + const pattern = `${p.namespace}/${p.name}`; + console.log(` ${status} ${pattern}`); + + if (p.namespace === '*' && p.name === '*') { + console.log(' ↳ This means ALL REPOS in ALL ORGS! šŸš€'); + } + }); + } + + console.log('\nšŸŽ‰ Loki can attest contributions to any GitHub repo!'); + } catch (error) { + console.log(`āŒ Error: ${error.message}`); + process.exit(1); + } +} + +main(); diff --git a/scripts/identity-utils/verify-binding.mjs b/scripts/identity-utils/verify-binding.mjs new file mode 100644 index 0000000..fbe14bc --- /dev/null +++ b/scripts/identity-utils/verify-binding.mjs @@ -0,0 +1,87 @@ +#!/usr/bin/env node +import { createPublicClient, http } from 'viem'; +import { baseSepolia } from 'viem/chains'; + +const RESOLVER_ADDRESS = '0x20c1cb4313efc28d325d3a893a68ca8c82911b0c'; +const RPC_URL = 'https://sepolia.base.org'; +const MY_WALLET = '0x7a1de0Fa7242194bbA84E915f39bF7E621B50d2E'; + +const RESOLVER_ABI = [ + { + type: 'function', + name: 'getIdentityOwner', + inputs: [ + { name: 'domain', type: 'string' }, + { name: 'identifier', type: 'string' } + ], + outputs: [{ name: 'owner', type: 'address' }], + stateMutability: 'view' + }, + { + type: 'function', + name: 'getRepositoryPatterns', + inputs: [ + { name: 'domain', type: 'string' }, + { name: 'identifier', type: 'string' } + ], + outputs: [{ + name: 'patterns', + type: 'tuple[]', + components: [ + { name: 'namespace', type: 'string' }, + { name: 'name', type: 'string' }, + { name: 'enabled', type: 'bool' } + ] + }], + stateMutability: 'view' + } +]; + +async function main() { + const client = createPublicClient({ + chain: baseSepolia, + transport: http(RPC_URL) + }); + + console.log('šŸ” Verifying identity binding...\n'); + + try { + const owner = await client.readContract({ + address: RESOLVER_ADDRESS, + abi: RESOLVER_ABI, + functionName: 'getIdentityOwner', + args: ['github.com', 'loki-cyberstorm'] + }); + + console.log(`āœ… Identity bound:`); + console.log(` github.com:loki-cyberstorm -> ${owner}`); + console.log(` Expected: ${MY_WALLET}`); + console.log(` Match: ${owner.toLowerCase() === MY_WALLET.toLowerCase() ? 'āœ…' : 'āŒ'}`); + } catch (error) { + console.log(`āŒ getIdentityOwner failed: ${error.message}`); + process.exit(1); + } + + console.log(); + + try { + const patterns = await client.readContract({ + address: RESOLVER_ADDRESS, + abi: RESOLVER_ABI, + functionName: 'getRepositoryPatterns', + args: ['github.com', 'loki-cyberstorm'] + }); + + console.log(`āœ… Repository patterns:`); + patterns.forEach(p => { + const status = p.enabled ? 'āœ…' : 'āŒ'; + console.log(` ${status} ${p.namespace}/${p.name}`); + }); + + console.log('\nšŸŽ‰ Success! Loki can now attest contributions to registered repos.'); + } catch (error) { + console.log(`āŒ getRepositoryPatterns failed: ${error.message}`); + } +} + +main();