@nexus-cross/pop 1.3.10-beta.2

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.
@@ -0,0 +1 @@
1
+ import{a as s,k as B}from"../chunk-KEW4FB4S.js";import{bytesToHex as Y,keccak256 as Q,recoverAddress as ee,toBytes as te}from"viem";var h=class{keccak256(e){return Q(te(e))}randomSecret(){let e=new Uint8Array(32);return ne().getRandomValues(e),Y(e).slice(2)}recoverAddress(e){return ee({hash:e.digest,signature:e.signature})}};function ne(){let r=globalThis.crypto;if(!r||typeof r.getRandomValues!="function")throw new Error("Secure crypto RNG (globalThis.crypto.getRandomValues) is unavailable");return r}import{serializeSignature as re,sign as ie}from"viem/accounts";var g=class{async signDigest(e){try{let t=await ie({hash:e.digest,privateKey:e.claimKey});return re(t)}catch(t){throw new s("SIGN_FAILED","Failed to sign claim digest with temp key",{cause:t instanceof Error?t.message:String(t)})}}};import{BaseError as se,ContractFunctionRevertedError as F,createWalletClient as ae,decodeFunctionResult as X,encodeFunctionData as oe,http as de,keccak256 as pe,parseSignature as ce,toBytes as $,toHex as ue}from"viem";import{privateKeyToAccount as le}from"viem/accounts";var b=[{type:"function",name:"deposit",stateMutability:"nonpayable",inputs:[{name:"claimAddr",type:"address"},{name:"amount",type:"uint256"},{name:"id",type:"string"},{name:"secretHash",type:"bytes32"},{name:"deadline",type:"uint256"},{name:"v",type:"uint8"},{name:"r",type:"bytes32"},{name:"s",type:"bytes32"}],outputs:[]},{type:"function",name:"token",stateMutability:"view",inputs:[],outputs:[{type:"address"}]}],L=[{type:"function",name:"drops",stateMutability:"view",inputs:[{name:"claimAddr",type:"address"}],outputs:[{name:"sponsor",type:"address"},{name:"amount",type:"uint256"},{name:"secretHash",type:"bytes32"},{name:"salt",type:"bytes32"},{name:"id",type:"string"}]}],A=[{type:"function",name:"name",stateMutability:"view",inputs:[],outputs:[{type:"string"}]},{type:"function",name:"nonces",stateMutability:"view",inputs:[{name:"owner",type:"address"}],outputs:[{type:"uint256"}]},{type:"function",name:"eip712Domain",stateMutability:"view",inputs:[],outputs:[{name:"fields",type:"bytes1"},{name:"name",type:"string"},{name:"version",type:"string"},{name:"chainId",type:"uint256"},{name:"verifyingContract",type:"address"},{name:"salt",type:"bytes32"},{name:"extensions",type:"uint256[]"}]}],M={Permit:[{name:"owner",type:"address"},{name:"spender",type:"address"},{name:"value",type:"uint256"},{name:"nonce",type:"uint256"},{name:"deadline",type:"uint256"}]};var u=[{type:"constructor",inputs:[{name:"claimPeriod_",type:"uint64",internalType:"uint64"}],stateMutability:"nonpayable"},{type:"function",name:"claimDigest",inputs:[{name:"recipient",type:"address",internalType:"address"},{name:"id",type:"string",internalType:"string"},{name:"salt",type:"bytes32",internalType:"bytes32"}],outputs:[{name:"",type:"bytes32",internalType:"bytes32"}],stateMutability:"view"},{type:"function",name:"claimPeriod",inputs:[],outputs:[{name:"",type:"uint64",internalType:"uint64"}],stateMutability:"view"},{type:"function",name:"claimedRecipientOf",inputs:[{name:"id",type:"string",internalType:"string"}],outputs:[{name:"",type:"address",internalType:"address"}],stateMutability:"view"},{type:"function",name:"deposit",inputs:[{name:"claimAddr",type:"address",internalType:"address"},{name:"token",type:"address",internalType:"contract IERC20"},{name:"amount",type:"uint256",internalType:"uint256"},{name:"id",type:"string",internalType:"string"},{name:"secretHash",type:"bytes32",internalType:"bytes32"}],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"depositCountOf",inputs:[{name:"sponsor",type:"address",internalType:"address"}],outputs:[{name:"",type:"uint256",internalType:"uint256"}],stateMutability:"view"},{type:"function",name:"depositsBySponsor",inputs:[{name:"sponsor",type:"address",internalType:"address"},{name:"",type:"uint256",internalType:"uint256"}],outputs:[{name:"claimAddr",type:"address",internalType:"address"},{name:"token",type:"address",internalType:"contract IERC20"},{name:"amount",type:"uint128",internalType:"uint128"},{name:"expiry",type:"uint64",internalType:"uint64"},{name:"secretHash",type:"bytes32",internalType:"bytes32"},{name:"id",type:"string",internalType:"string"}],stateMutability:"view"},{type:"function",name:"depositsOf",inputs:[{name:"sponsor",type:"address",internalType:"address"}],outputs:[{name:"",type:"tuple[]",internalType:"struct SafeDrop.DepositRecord[]",components:[{name:"claimAddr",type:"address",internalType:"address"},{name:"token",type:"address",internalType:"contract IERC20"},{name:"amount",type:"uint128",internalType:"uint128"},{name:"expiry",type:"uint64",internalType:"uint64"},{name:"secretHash",type:"bytes32",internalType:"bytes32"},{name:"id",type:"string",internalType:"string"}]}],stateMutability:"view"},{type:"function",name:"drops",inputs:[{name:"claimAddr",type:"address",internalType:"address"}],outputs:[{name:"sponsor",type:"address",internalType:"address"},{name:"recordIndex",type:"uint96",internalType:"uint96"},{name:"token",type:"address",internalType:"contract IERC20"},{name:"amount",type:"uint128",internalType:"uint128"},{name:"expiry",type:"uint64",internalType:"uint64"},{name:"secretHash",type:"bytes32",internalType:"bytes32"},{name:"salt",type:"bytes32",internalType:"bytes32"},{name:"id",type:"string",internalType:"string"}],stateMutability:"view"},{type:"function",name:"eip712Domain",inputs:[],outputs:[{name:"fields",type:"bytes1",internalType:"bytes1"},{name:"name",type:"string",internalType:"string"},{name:"version",type:"string",internalType:"string"},{name:"chainId",type:"uint256",internalType:"uint256"},{name:"verifyingContract",type:"address",internalType:"address"},{name:"salt",type:"bytes32",internalType:"bytes32"},{name:"extensions",type:"uint256[]",internalType:"uint256[]"}],stateMutability:"view"},{type:"function",name:"liveClaimAddrsById",inputs:[{name:"id",type:"string",internalType:"string"}],outputs:[{name:"",type:"address[]",internalType:"address[]"}],stateMutability:"view"},{type:"function",name:"liveDropCountById",inputs:[{name:"id",type:"string",internalType:"string"}],outputs:[{name:"",type:"uint256",internalType:"uint256"}],stateMutability:"view"},{type:"function",name:"liveDropsById",inputs:[{name:"id",type:"string",internalType:"string"}],outputs:[{name:"claimAddrs",type:"address[]",internalType:"address[]"},{name:"records",type:"tuple[]",internalType:"struct SafeDrop.Drop[]",components:[{name:"sponsor",type:"address",internalType:"address"},{name:"recordIndex",type:"uint96",internalType:"uint96"},{name:"token",type:"address",internalType:"contract IERC20"},{name:"amount",type:"uint128",internalType:"uint128"},{name:"expiry",type:"uint64",internalType:"uint64"},{name:"secretHash",type:"bytes32",internalType:"bytes32"},{name:"salt",type:"bytes32",internalType:"bytes32"},{name:"id",type:"string",internalType:"string"}]}],stateMutability:"view"},{type:"function",name:"refund",inputs:[{name:"claimAddr",type:"address",internalType:"address"}],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"unclaimedCountOf",inputs:[{name:"sponsor",type:"address",internalType:"address"}],outputs:[{name:"",type:"uint256",internalType:"uint256"}],stateMutability:"view"},{type:"function",name:"unclaimedDepositsOf",inputs:[{name:"sponsor",type:"address",internalType:"address"}],outputs:[{name:"records",type:"tuple[]",internalType:"struct SafeDrop.DepositRecord[]",components:[{name:"claimAddr",type:"address",internalType:"address"},{name:"token",type:"address",internalType:"contract IERC20"},{name:"amount",type:"uint128",internalType:"uint128"},{name:"expiry",type:"uint64",internalType:"uint64"},{name:"secretHash",type:"bytes32",internalType:"bytes32"},{name:"id",type:"string",internalType:"string"}]}],stateMutability:"view"},{type:"function",name:"unclaimedIndexesOf",inputs:[{name:"sponsor",type:"address",internalType:"address"}],outputs:[{name:"",type:"uint256[]",internalType:"uint256[]"}],stateMutability:"view"},{type:"function",name:"withdraw",inputs:[{name:"recipient",type:"address",internalType:"address"},{name:"id",type:"string",internalType:"string"},{name:"salt",type:"bytes32",internalType:"bytes32"},{name:"signature",type:"bytes",internalType:"bytes"},{name:"secret",type:"bytes",internalType:"bytes"}],outputs:[],stateMutability:"nonpayable"},{type:"event",name:"Deposited",inputs:[{name:"claimAddr",type:"address",indexed:!0,internalType:"address"},{name:"sponsor",type:"address",indexed:!0,internalType:"address"},{name:"token",type:"address",indexed:!0,internalType:"address"},{name:"amount",type:"uint256",indexed:!1,internalType:"uint256"},{name:"id",type:"string",indexed:!1,internalType:"string"},{name:"expiry",type:"uint64",indexed:!1,internalType:"uint64"},{name:"secretHash",type:"bytes32",indexed:!1,internalType:"bytes32"}],anonymous:!1},{type:"event",name:"EIP712DomainChanged",inputs:[],anonymous:!1},{type:"event",name:"Refunded",inputs:[{name:"claimAddr",type:"address",indexed:!0,internalType:"address"},{name:"sponsor",type:"address",indexed:!0,internalType:"address"},{name:"id",type:"string",indexed:!1,internalType:"string"},{name:"amount",type:"uint256",indexed:!1,internalType:"uint256"}],anonymous:!1},{type:"event",name:"Withdrawn",inputs:[{name:"claimAddr",type:"address",indexed:!0,internalType:"address"},{name:"recipient",type:"address",indexed:!0,internalType:"address"},{name:"sponsor",type:"address",indexed:!0,internalType:"address"},{name:"id",type:"string",indexed:!1,internalType:"string"},{name:"amount",type:"uint256",indexed:!1,internalType:"uint256"}],anonymous:!1},{type:"error",name:"AmountOverflow",inputs:[]},{type:"error",name:"DropAlreadyExists",inputs:[]},{type:"error",name:"DropExpired",inputs:[]},{type:"error",name:"DropNotExpired",inputs:[]},{type:"error",name:"DropNotFound",inputs:[]},{type:"error",name:"DuplicateDropForId",inputs:[]},{type:"error",name:"ECDSAInvalidSignature",inputs:[]},{type:"error",name:"ECDSAInvalidSignatureLength",inputs:[{name:"length",type:"uint256",internalType:"uint256"}]},{type:"error",name:"ECDSAInvalidSignatureS",inputs:[{name:"s",type:"bytes32",internalType:"bytes32"}]},{type:"error",name:"EmptyId",inputs:[]},{type:"error",name:"IdMismatch",inputs:[]},{type:"error",name:"InvalidShortString",inputs:[]},{type:"error",name:"NotSponsor",inputs:[]},{type:"error",name:"ReentrancyGuardReentrantCall",inputs:[]},{type:"error",name:"SafeERC20FailedOperation",inputs:[{name:"token",type:"address",internalType:"address"}]},{type:"error",name:"SaltMismatch",inputs:[]},{type:"error",name:"SecretMismatch",inputs:[]},{type:"error",name:"StringTooLong",inputs:[{name:"str",type:"string",internalType:"string"}]},{type:"error",name:"ZeroAmount",inputs:[]},{type:"error",name:"ZeroClaimAddress",inputs:[]},{type:"error",name:"ZeroRecipient",inputs:[]},{type:"error",name:"ZeroToken",inputs:[]}];var l=[{type:"function",name:"validator",stateMutability:"view",inputs:[],outputs:[{type:"address"}]},{type:"function",name:"validatorClaimDigest",stateMutability:"view",inputs:[{name:"recipient",type:"address"},{name:"id",type:"string"},{name:"nonce",type:"uint256"},{name:"deadline",type:"uint256"}],outputs:[{type:"bytes32"}]},{type:"function",name:"validatorNonceById",stateMutability:"view",inputs:[{name:"idHash",type:"bytes32"}],outputs:[{type:"uint256"}]},{type:"function",name:"withdrawByValidator",stateMutability:"nonpayable",inputs:[{name:"recipient",type:"address"},{name:"id",type:"string"},{name:"nonce",type:"uint256"},{name:"deadline",type:"uint256"},{name:"maxCount",type:"uint256"},{name:"signature",type:"bytes"}],outputs:[]},{type:"function",name:"liveDropCountById",stateMutability:"view",inputs:[{name:"id",type:"string"}],outputs:[{type:"uint256"}]},{type:"function",name:"claimedRecipientOf",stateMutability:"view",inputs:[{name:"id",type:"string"}],outputs:[{type:"address"}]},{type:"error",name:"ValidatorSigExpired",inputs:[]},{type:"error",name:"InvalidValidatorNonce",inputs:[]}];var me=[{type:"function",name:"approve",stateMutability:"nonpayable",inputs:[{name:"spender",type:"address"},{name:"value",type:"uint256"}],outputs:[{name:"",type:"bool"}]}],ye="0x0000000000000000000000000000000000000000",he={gas:BigInt(3e5),maxFeePerGas:BigInt(4e9),maxPriorityFeePerGas:BigInt(1e9)},ge=BigInt(3600),f=class{constructor(e){this.address=e.address,this.publicClient=e.publicClient,this.walletClient=e.walletClient,this.chain=e.chain,this.transport=e.transport??de(),this.withdrawFees={...he,...e.withdrawFees},e.permit&&(this.depositWithPermit=t=>this.permitDeposit(t))}async getDrop(e){try{let{data:t}=await this.publicClient.call({to:this.address,data:oe({abi:u,functionName:"drops",args:[e]})});if(!t||t==="0x")return null;let n;try{let[i,,o,d,c,m,T,_]=X({abi:u,functionName:"drops",data:t});n={claimAddress:e,sponsor:i,token:o,amount:d,expiry:Number(c),secretHash:m,salt:T,id:_}}catch{let[i,o,d,c,m]=X({abi:L,functionName:"drops",data:t});n={claimAddress:e,sponsor:i,amount:o,secretHash:d,salt:c,id:m}}return!n.sponsor||n.sponsor.toLowerCase()===ye?null:(n.token||(n.token=await this.escrowToken()),n)}catch(t){throw p("getDrop",t)}}async escrowToken(){try{return await this.publicClient.readContract({address:this.address,abi:b,functionName:"token"})}catch{return}}async getClaimDigest(e){try{return await this.publicClient.readContract({address:this.address,abi:u,functionName:"claimDigest",args:[e.recipient,e.id,e.salt]})}catch(t){throw p("claimDigest",t)}}async approve(e){return this.sponsorWrite("approve",()=>this.walletClient.writeContract({address:e.token,abi:me,functionName:"approve",args:[this.address,e.amount],account:this.requireAccount(),chain:this.chain}))}async deposit(e){let t=[e.claimAddress,e.token,e.amount,e.id,e.secretHash];try{await this.publicClient.simulateContract({address:this.address,abi:u,functionName:"deposit",args:t,account:this.requireAccount()})}catch(i){throw p("deposit",i)}return{txHash:await this.sponsorWrite("deposit",()=>this.walletClient.writeContract({address:this.address,abi:u,functionName:"deposit",args:t,account:this.requireAccount(),chain:this.chain}))}}async permitDeposit(e){try{let t=this.requireAccount(),n=e.token,[i,o,d]=await Promise.all([this.publicClient.readContract({address:this.address,abi:b,functionName:"token"}),this.publicClient.readContract({address:n,abi:A,functionName:"nonces",args:[t.address]}),this.permitDomain(n)]);if(i.toLowerCase()!==n.toLowerCase())throw new s("INVALID_TOKEN","token does not match the escrow token",{expected:i,received:e.token});let c=BigInt(Math.floor(Date.now()/1e3))+ge,m=await this.walletClient.signTypedData({account:t,domain:{...d,chainId:this.chain.id,verifyingContract:n},types:M,primaryType:"Permit",message:{owner:t.address,spender:this.address,value:e.amount,nonce:o,deadline:c}}),{r:T,s:_,v:J,yParity:Z}=ce(m),V=await this.walletClient.writeContract({address:this.address,abi:b,functionName:"deposit",args:[e.claimAddress,e.amount,e.id,e.secretHash,c,Number(J??BigInt(Z+27)),T,_],account:t,chain:this.chain});return await this.confirm("depositWithPermit",V),{txHash:V}}catch(t){throw p("depositWithPermit",t)}}async permitDomain(e){try{let t=await this.publicClient.readContract({address:e,abi:A,functionName:"eip712Domain"});return{name:t[1],version:t[2]}}catch{return{name:await this.publicClient.readContract({address:e,abi:A,functionName:"name"}),version:"1"}}}async withdraw(e){try{let t=le(e.claimKey),i=await ae({account:t,chain:this.chain,transport:this.transport}).writeContract({address:this.address,abi:u,functionName:"withdraw",args:[e.recipient,e.id,e.salt,e.signature,ue($(e.secret))],gas:this.withdrawFees.gas,maxFeePerGas:this.withdrawFees.maxFeePerGas,maxPriorityFeePerGas:this.withdrawFees.maxPriorityFeePerGas});return await this.confirm("withdraw",i),i}catch(t){throw p("withdraw",t)}}async getValidatorNonce(e){try{return await this.publicClient.readContract({address:this.address,abi:l,functionName:"validatorNonceById",args:[pe($(e.id))]})}catch(t){throw p("validatorNonceById",t)}}async getValidatorClaimDigest(e){try{return await this.publicClient.readContract({address:this.address,abi:l,functionName:"validatorClaimDigest",args:[e.recipient,e.id,e.nonce,e.deadline]})}catch(t){throw p("validatorClaimDigest",t)}}async getValidator(){try{return await this.publicClient.readContract({address:this.address,abi:l,functionName:"validator"})}catch(e){throw p("validator",e)}}async getClaimedRecipient(e){try{return await this.publicClient.readContract({address:this.address,abi:l,functionName:"claimedRecipientOf",args:[e.id]})}catch(t){throw p("claimedRecipientOf",t)}}async getLiveDropCount(e){try{return await this.publicClient.readContract({address:this.address,abi:l,functionName:"liveDropCountById",args:[e.id]})}catch(t){throw p("liveDropCountById",t)}}async withdrawByValidator(e){let t=[e.recipient,e.id,e.nonce,e.deadline,BigInt(e.maxCount),e.signature],n=this.requireAccount();if(n.address.toLowerCase()!==e.recipient.toLowerCase())throw new s("CHAIN_ERROR","withdrawByValidator must be sent by the recipient wallet",{connected:n.address,recipient:e.recipient});try{await this.publicClient.simulateContract({address:this.address,abi:l,functionName:"withdrawByValidator",args:t,account:n})}catch(i){throw p("withdrawByValidator",i)}return this.sponsorWrite("withdrawByValidator",()=>this.walletClient.writeContract({address:this.address,abi:l,functionName:"withdrawByValidator",args:t,account:n,chain:this.chain}))}async refund(e){return this.sponsorWrite("refund",()=>this.walletClient.writeContract({address:this.address,abi:u,functionName:"refund",args:[e.claimAddress],account:this.requireAccount(),chain:this.chain}))}async sponsorWrite(e,t){try{let n=await t();return await this.confirm(e,n),n}catch(n){throw p(e,n)}}async confirm(e,t){let n=await this.publicClient.waitForTransactionReceipt({hash:t});if(n.status!=="success"){let i=await this.recoverRevertName(t,n.blockNumber),o=i?`SafeDrop.${e} reverted: ${i}`:`transaction reverted: ${t}`;throw new s("CHAIN_ERROR",o,{revertName:i,txHash:t})}}async recoverRevertName(e,t){try{let n=await this.publicClient.getTransaction({hash:e});await this.publicClient.call({account:n.from,to:n.to??void 0,data:n.input,value:n.value,blockNumber:t});return}catch(n){return q(n)}}requireAccount(){let e=this.walletClient.account;if(!e)throw new s("CHAIN_ERROR","walletClient has no account (connect a wallet first)");return e}};function p(r,e){if(e instanceof s)return e;let t=q(e),n=e instanceof Error?e.message:String(e),i=t?`SafeDrop.${r} reverted: ${t}`:`SafeDrop.${r} failed`;return new s("CHAIN_ERROR",i,{cause:n,revertName:t})}function q(r){if(!(r instanceof se))return;let e=r.walk(t=>t instanceof F);if(e instanceof F)return e.data?.errorName??e.reason??void 0}var fe={dev:"https://dev-one-pop-api.onechain.nexus/api",stage:"https://stg-one-pop-api.onechain.nexus/api",production:"https://one-pop-api.onechain.nexus/api"},C={createWallet:"/wallets",retrievePrivateKey:"/wallets/private-key",drops:"/drops",envelopes:"/envelopes",leaderboards:"/leaderboards",histories:"/histories",xConnections:"/x-connections",batchClaimSignature:"/batch-claim-signature",eventsSubscribe:"/events/subscribe"},we="https://dev-cross-auth.crosstoken.io";function E(r){try{return import.meta.env?.[r]}catch{return}}function P(r){if(!(typeof process>"u"||!process.env))switch(r){case"NEXT_PUBLIC_ONE_POP_ENVIRONMENT":return process.env.NEXT_PUBLIC_ONE_POP_ENVIRONMENT;case"ONE_POP_ENVIRONMENT":return process.env.ONE_POP_ENVIRONMENT;case"NEXT_PUBLIC_ONE_POP_API_BASE_URL":return process.env.NEXT_PUBLIC_ONE_POP_API_BASE_URL;case"NEXT_PUBLIC_CROSS_AUTH_URL":return process.env.NEXT_PUBLIC_CROSS_AUTH_URL;default:return}}function be(){switch((E("VITE_ONE_POP_ENVIRONMENT")??P("NEXT_PUBLIC_ONE_POP_ENVIRONMENT")??P("ONE_POP_ENVIRONMENT"))?.toLowerCase()){case"dev":case"development":return"dev";case"stg":case"stage":case"staging":return"stage";default:return"production"}}function I(){let e=E("VITE_ONE_POP_API_BASE_URL")??P("NEXT_PUBLIC_ONE_POP_API_BASE_URL")??fe[be()];return W(e),e}function S(){let e=(E("VITE_CROSS_AUTH_URL")??P("NEXT_PUBLIC_CROSS_AUTH_URL")??we).replace(/\/+$/,"");return W(e),e}function W(r){let e;try{e=new URL(r)}catch{throw new Error(`[pop] Invalid base URL: ${r}`)}if(e.protocol==="https:")return;let t=e.hostname==="localhost"||e.hostname==="127.0.0.1"||e.hostname.endsWith(".local");if(!(e.protocol==="http:"&&t))throw new Error(`[pop] base URL must be https (or http://localhost for dev). Got: ${r}`)}var Ae={INVALID_PARAM:"INVALID_PARAM",RATE_LIMITED:"RATE_LIMITED",INVALID_OAUTH_TOKEN:"INVALID_OAUTH_TOKEN",UNAUTHORIZED:"UNAUTHORIZED",WALLET_NOT_FOUND:"WALLET_NOT_FOUND",SEND_BLOCKED:"SEND_BLOCKED",ENVELOPE_NOT_FOUND:"ENVELOPE_NOT_FOUND",PENDING_LIMIT_EXCEEDED:"PENDING_LIMIT_EXCEEDED",X_CONNECTION_NOT_FOUND:"X_CONNECTION_NOT_FOUND"},G=140,w=class{constructor(e={}){this.baseUrl=(e.baseUrl??I()).replace(/\/+$/,"");let t=e.fetchImpl??globalThis.fetch;if(!t)throw new Error("fetch is unavailable; provide options.fetchImpl");this.fetchImpl=t,this.getJwt=e.getJwt,this.paths={...C,...e.paths}}async createClaimWallet(e){if(e.message&&[...e.message].length>G)throw new s("INVALID_PARAM",`message must be <= ${G} runes`,{length:[...e.message].length});let t=await this.request(this.paths.createWallet,{method:"POST",auth:!0,payload:{identifier:e.recipient.handle,oauth_type:e.recipient.provider,sender_address:e.sender,...e.message?{message:e.message}:{},...e.envelopeId?{envelope_id:e.envelopeId}:{}}});if(!t?.address)throw new s("API_ERROR","createClaimWallet: missing address in response",{body:t});return{claimAddress:t.address}}async retrieveClaimKey(e){let t=await this.request(this.paths.retrievePrivateKey,{method:"POST",payload:{oauth_token:e.oauth.accessToken,oauth_type:e.oauth.provider,sender_address:e.senderAddress}});if(!t?.private_key||!t.address)throw new s("API_ERROR","retrieveClaimKey: missing key/address in response",{body:t});return{claimKey:t.private_key,claimAddress:t.address}}async listDrops(e={}){let t=await this.request(this.paths.drops,{method:"GET",auth:!0,query:{token:e.token}});return{items:(t?.items??[]).map(Te),count:t?.count??0,totalAmount:y(t?.total_amount,"drops.total_amount"),hasClaimedBefore:t?.has_claimed_before===!0}}async listEnvelopes(e={}){return((await this.request(this.paths.envelopes,{method:"GET",query:{locale:e.locale}}))?.items??[]).map(n=>({id:a(n.id),name:a(n.name),imageUrl:a(n.image_url),badge:a(n.badge),sortOrder:Number(n.sort_order??0)}))}async getLeaderboard(e){if(!e.token)throw new s("INVALID_PARAM","getLeaderboard requires a token address");return((await this.request(this.paths.leaderboards,{method:"GET",query:{token:e.token}}))?.items??[]).map(n=>({identifier:a(n.identifier),balance:y(n.balance,"leaderboard.balance"),lastDepositAmount:y(n.last_deposit_amount,"leaderboard.last_deposit_amount")}))}async listHistories(e={}){let t=await this.request(this.paths.histories,{method:"GET",auth:!0,query:{event:e.event,page:e.page,page_size:e.pageSize}});return{items:(t?.items??[]).map(n=>({eventName:a(n.event_name),txHash:a(n.tx_hash),blockNumber:Number(n.block_number??0),logIndex:Number(n.log_index??0),contractAddress:a(n.contract_address),fromAddress:v(n.from_address),toAddress:v(n.to_address),tempTo:v(n.temp_to),identifier:a(n.identifier),amount:y(n.amount,"history.amount"),createdAt:a(n.created_at),payload:n.payload??void 0})),page:t?.page??1,pageSize:t?.page_size??0,total:t?.total??0}}async getXConnection(){let e=await this.request(this.paths.xConnections,{method:"GET",auth:!0,notFoundAsNull:!0});return e?K(e):null}async connectX(e){let t=await this.request(this.paths.xConnections,{method:"POST",auth:!0,payload:{oauth_token:e.oauth.accessToken}});return K(t??{})}async disconnectX(){await this.request(this.paths.xConnections,{method:"DELETE",auth:!0,notFoundAsNull:!0})}async requestBatchClaimSignature(e){let t=await this.request(this.paths.batchClaimSignature,{method:"POST",auth:!0,payload:{id:e.id,oauth_token:e.oauth.accessToken,nonce:e.nonce.toString(),deadline:e.deadline.toString()}}),n=a(t?.signature);if(!n)throw new s("API_ERROR","requestBatchClaimSignature: missing signature",{body:t});return{id:a(t?.id)||e.id,recipient:a(t?.recipient),nonce:j(t?.nonce)?y(t?.nonce,"batchClaim.nonce"):e.nonce,deadline:j(t?.deadline)?y(t?.deadline,"batchClaim.deadline"):e.deadline,signature:n}}async request(e,t){let n={};if(t.payload&&(n["Content-Type"]="application/json"),t.auth){let o=await this.getJwt?.();if(!o)throw new s("UNAUTHORIZED",`${e} requires a SIWE JWT (options.getJwt)`);n.Authorization=`Bearer ${o}`}let i;try{i=await this.fetchImpl(`${this.baseUrl}${e}${Pe(t.query)}`,{method:t.method,headers:n,body:t.payload?JSON.stringify(t.payload):void 0})}catch(o){throw new s("API_ERROR",`Network error calling ${e}`,{cause:o instanceof Error?o.message:String(o)})}if(i.status===404&&t.notFoundAsNull)return null;if(!i.ok){let o=await i.json().catch(()=>{}),d=o?.code_name;throw new s((d?Ae[d]:void 0)??"API_ERROR",o?.message?`${e}: ${o.message}`:`${e} responded ${i.status}`,{status:i.status,code:o?.code,codeName:d})}return i.status===204?null:await i.json().catch(()=>null)}};function Pe(r){if(!r)return"";let e=new URLSearchParams;for(let[n,i]of Object.entries(r))i!==void 0&&i!==""&&e.set(n,String(i));let t=e.toString();return t?`?${t}`:""}function j(r){return r!=null&&r!==""}function a(r){return typeof r=="string"?r:""}function v(r){return typeof r=="string"&&r?r:null}function y(r,e){if(typeof r=="bigint")return r;if(typeof r=="number"){if(!Number.isSafeInteger(r))throw new s("API_ERROR",`${e} exceeds safe-integer precision as a JSON number`,{value:r});return BigInt(r)}let t=typeof r=="string"?r.trim():"";if(!t)return BigInt(0);try{return BigInt(t)}catch{throw new s("API_ERROR",`${e} is not a valid decimal string`,{value:r})}}function Te(r){let e=r??{},t=e.sender??{};return{claimAddress:a(e.claim_address),token:a(e.token),amount:y(e.amount,"drop.amount"),message:a(e.message),envelopeId:a(e.envelope_id),depositedAt:a(e.deposited_at),sender:{address:a(t.address),handle:a(t.handle),displayName:a(t.display_name),profileImageUrl:a(t.profile_image_url)}}}function K(r){return{xUserId:a(r.x_user_id),handle:a(r.handle),displayName:a(r.display_name),profileImageUrl:a(r.profile_image_url),walletAddress:a(r.wallet_address)}}var R=class{constructor(e={}){this.baseUrl=(e.baseUrl??S()).replace(/\/+$/,""),this.domain=e.domain??globalThis.location?.origin??"";let t=e.fetchImpl??globalThis.fetch;if(!t)throw new Error("fetch is unavailable; provide options.fetchImpl");this.fetchImpl=t}unsignedHash(e,t){return this.post("/login/unsigned-hash",{chain_id:e,address:t,domain:this.domain})}siweToken(e,t){return this.post("/login/token",{address:e,signature:t,domain:this.domain})}async post(e,t){let n;try{n=await this.fetchImpl(`${this.baseUrl}/cross-auth${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}catch(o){throw new s("API_ERROR",`cross-auth network error calling ${e}`,{cause:o instanceof Error?o.message:String(o)})}let i=await n.json().catch(()=>({}));if(!n.ok||i.data==null)throw new s("API_ERROR",`cross-auth ${e} failed`,{code:i.code,message:i.message});return i.data}};function _e(r){let e=r.apiPort??new w(r.api),t=new f({address:r.contractAddress,publicClient:r.publicClient,walletClient:r.walletClient,chain:r.chain,transport:r.transport,withdrawFees:r.withdrawFees,permit:r.permit});return B({claimBaseUrl:r.claimBaseUrl},{api:e,chain:t,signer:new g,crypto:new h})}async function O(){let r=x(z(32)),e=await Ce().digest("SHA-256",Ee(r)),t=x(new Uint8Array(e));return{verifier:r,challenge:t,method:"S256"}}function D(){return x(z(16))}function z(r){let e=globalThis.crypto;if(!e?.getRandomValues)throw new Error("crypto.getRandomValues unavailable");let t=new Uint8Array(r);return e.getRandomValues(t),t}function Ce(){let r=globalThis.crypto?.subtle;if(!r)throw new Error("crypto.subtle unavailable (needs https/secure context)");return r}function Ee(r){return new TextEncoder().encode(r)}function x(r){let e="";for(let n of r)e+=String.fromCharCode(n);let t=globalThis.btoa?.(e);if(t===void 0)throw new Error("btoa unavailable");return t.replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}var N=class{constructor(e={}){this.baseUrl=(e.baseUrl??"").replace(/\/+$/,""),this.tokenPath=e.tokenPath??"/api/x/token",this.mePath=e.mePath??"/api/x/me";let t=e.fetchImpl??globalThis.fetch;if(!t)throw new Error("fetch is unavailable; provide options.fetchImpl");this.fetchImpl=t}async exchangeCode(e){let n=await(await this.call(this.tokenPath,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({clientId:e.clientId,redirectUri:e.redirectUri,code:e.code,verifier:e.codeVerifier})})).json().catch(()=>({}));if(!n.access_token)throw new s("API_ERROR","X token exchange: missing access_token",{body:n});return{accessToken:n.access_token,scope:n.scope,expiresIn:n.expires_in,refreshToken:n.refresh_token}}async getHandle(e){let n=await(await this.call(this.mePath,{method:"GET",headers:{Authorization:`Bearer ${e}`}})).json().catch(()=>({})),i=n.data?.username??n.username;if(!i)throw new s("API_ERROR","X /me: missing username",{body:n});return{handle:i}}async call(e,t){let n;try{n=await this.fetchImpl(`${this.baseUrl}${e}`,t)}catch(i){throw new s("API_ERROR",`Network error calling ${e}`,{cause:i instanceof Error?i.message:String(i)})}if(!n.ok)throw new s("API_ERROR",`${e} responded ${n.status}`,{status:n.status});return n}};var k="pop.xauth.verifier",U="pop.xauth.state",Ie="https://x.com/i/oauth2/authorize",Se="tweet.read users.read offline.access",H=class{constructor(e){this.opts={clientId:e.clientId,redirectUri:e.redirectUri,scope:e.scope??Se,authorizeUrl:e.authorizeUrl??Ie,port:e.port,storage:e.storage??ve()}}async start(){let e=await O(),t=D();this.opts.storage.set(k,e.verifier),this.opts.storage.set(U,t);let n=new URLSearchParams({response_type:"code",client_id:this.opts.clientId,redirect_uri:this.opts.redirectUri,scope:this.opts.scope,state:t,code_challenge:e.challenge,code_challenge_method:"S256"});return{authorizeUrl:`${this.opts.authorizeUrl}?${n.toString()}`,state:t}}async complete(e){let t=new URL(e),n=t.searchParams.get("code"),i=t.searchParams.get("state");if(!n)throw new s("API_ERROR","X callback: missing authorization code",{error:t.searchParams.get("error")});let o=this.opts.storage.get(U);if(!i||!o||i!==o)throw new s("API_ERROR","X callback: state mismatch (possible CSRF)");let d=this.opts.storage.get(k);if(!d)throw new s("API_ERROR","X callback: missing PKCE verifier (expired session?)");let c=await this.opts.port.exchangeCode({clientId:this.opts.clientId,redirectUri:this.opts.redirectUri,code:n,codeVerifier:d}),{handle:m}=await this.opts.port.getHandle(c.accessToken);return this.opts.storage.remove(k),this.opts.storage.remove(U),{oauth:{provider:"x",accessToken:c.accessToken},handle:m}}};function ve(){let r=globalThis.sessionStorage;if(!r)throw new Error("sessionStorage unavailable; provide options.storage");return{get:e=>r.getItem(e),set:(e,t)=>r.setItem(e,t),remove:e=>r.removeItem(e)}}export{R as CrossAuthClient,C as DEFAULT_POP_API_PATHS,w as HttpSafeDropApiAdapter,N as HttpXAuthAdapter,u as SAFEDROP_ABI,l as SAFEDROP_VALIDATOR_ABI,g as ViemClaimSignerAdapter,h as ViemCryptoAdapter,f as ViemSafeDropChainAdapter,H as XAuthClient,_e as createSafeDropClient,O as generatePkce,D as generateState,S as getCrossAuthBaseUrl,I as getOnePopApiBaseUrl};
@@ -0,0 +1 @@
1
+ var r=class extends Error{constructor(e,t,n){super(t),this.name="SafeDropError",this.code=e,this.details=n}};function m(i){return i.trim().replace(/^@/,"").toLowerCase()}function w(i){let e=i.baseUrl.replace(/\/$/,""),t=e.includes("?")?"&":"?";return`${e}${t}sender=${encodeURIComponent(i.sender)}#${encodeURIComponent(i.secret)}`}function b(i){let e=i.indexOf("#"),t=e===-1?i:i.slice(0,e),n=e===-1?"":i.slice(e+1),a=t.indexOf("?"),c=a===-1?"":t.slice(a+1);return{sender:E(c,"sender"),secret:n?decodeURIComponent(n):null}}function O(i){return`\u{1F381} You've received a one-pop drop! Claim your tokens here: ${i}`}function T(i,e){let t=[`text=${encodeURIComponent(i)}`];return e&&t.push(`recipient_id=${encodeURIComponent(e)}`),`https://x.com/messages/compose?${t.join("&")}`}function E(i,e){if(!i)return null;for(let t of i.split("&")){if(!t)continue;let n=t.indexOf("=");if(decodeURIComponent(n===-1?t:t.slice(0,n))===e)return decodeURIComponent(n===-1?"":t.slice(n+1))}return null}var u=class{constructor(e,t,n,a){this.chain=e;this.api=t;this.crypto=n;this.claimBaseUrl=a}async execute(e){if(!e.sender)throw new r("MISSING_SENDER","sender address is required");if(!e.recipient?.handle)throw new r("MISSING_RECIPIENT","recipient social handle is required");if(!e.token)throw new r("INVALID_TOKEN","token address is required (ERC20 only)");if(e.amount<=0n)throw new r("INVALID_AMOUNT","amount must be a positive BigInt");let t=e.secret??this.crypto.randomSecret();if(!t)throw new r("MISSING_SECRET","secret is empty");let n=this.crypto.keccak256(t),a=m(e.recipient.handle);if(!a)throw new r("MISSING_RECIPIENT","recipient handle normalizes to empty");let{claimAddress:c}=await this.api.createClaimWallet({sender:e.sender,recipient:e.recipient,message:e.message,envelopeId:e.envelopeId}),s={claimAddress:c,token:e.token,amount:e.amount,id:a,secretHash:n},{txHash:o}=this.chain.depositWithPermit?await this.chain.depositWithPermit(s):await this.approveThenDeposit(s);return{claimAddress:c,secretHash:n,withdrawLink:w({baseUrl:this.claimBaseUrl,sender:e.sender,secret:t}),depositTxHash:o}}approveThenDeposit(e){return this.chain.approve({token:e.token,amount:e.amount}).then(()=>this.chain.deposit(e))}};var N=BigInt(1800),_=30,f=class{constructor(e,t,n,a=()=>Date.now()){this.chain=e;this.api=t;this.crypto=n;this.now=a}async execute(e){let t=m(e.id);if(!t)throw new r("MISSING_RECIPIENT","batchClaim requires an id (social handle)");let n=this.api.requestBatchClaimSignature;if(!n)throw new r("API_ERROR","SafeDropApiPort.requestBatchClaimSignature is not implemented by the injected api adapter");let{getValidatorNonce:a,getValidatorClaimDigest:c,getValidator:s,withdrawByValidator:o}=this.chain;if(!a||!c||!s||!o)throw new r("CHAIN_ERROR","this chain adapter does not support batch claim (validator functions missing)");let I=await a.call(this.chain,{id:t}),A=e.deadline??BigInt(Math.floor(this.now()/1e3))+(e.deadlineTtlSeconds??N),d=await n.call(this.api,{id:t,oauth:e.oauth,nonce:I,deadline:A}),l=d.recipient;if(!l)throw new r("API_ERROR","batch-claim signature response has no recipient",{signed:d});if(e.recipient&&e.recipient.toLowerCase()!==l.toLowerCase())throw new r("SIGN_FAILED","validator signature is bound to a different recipient",{requested:e.recipient,signed:l});await this.assertRecipientPinnedOnChain(t,l);let R=await c.call(this.chain,{recipient:l,id:t,nonce:d.nonce,deadline:d.deadline});await this.assertSignedByValidator(R,d.signature,s.bind(this.chain));let x=e.maxCount??_,y=[],h=await this.liveCount(t);for(let g=0;h>BigInt(0);g+=1){let P=await o.call(this.chain,{recipient:l,id:t,nonce:d.nonce,deadline:d.deadline,maxCount:x,signature:d.signature});y.push(P);let D=await this.liveCount(t);if(D>=h)throw new r("CHAIN_ERROR","batch claim made no progress",{id:t,remaining:h.toString(),page:g,txHash:P});h=D}return{txHashes:y,recipient:l,id:t,nonce:d.nonce,deadline:d.deadline}}async assertRecipientPinnedOnChain(e,t){let n=this.chain.getClaimedRecipient;if(!n)return;let a=await n.call(this.chain,{id:e});if(!a||/^0x0+$/i.test(a))throw new r("DROP_NOT_FOUND","this id has not claimed individually yet \u2014 the first claim must be an individual withdraw",{id:e});if(a.toLowerCase()!==t.toLowerCase())throw new r("SIGN_FAILED","batch-claim recipient does not match the on-chain claimedRecipientOf(id)",{id:e,signed:t,onChain:a})}async assertSignedByValidator(e,t,n){if(!this.crypto.recoverAddress)return;let[a,c]=await Promise.all([this.crypto.recoverAddress({digest:e,signature:t}),n()]);if(a.toLowerCase()!==c.toLowerCase())throw new r("SIGN_FAILED","batch-claim signature does not recover to the on-chain validator (backend signed a different EIP-712 domain/type, or applied a personal_sign prefix)",{signer:a,validator:c,digest:e})}async liveCount(e){let t=this.chain.getLiveDropCount;if(!t)throw new r("CHAIN_ERROR","this chain adapter cannot read liveDropCountById (required to paginate batch claim)");return t.call(this.chain,{id:e})}};var C=class{constructor(e,t){this.chain=e;this.signer=t}async execute(e){if(!e.recipient)throw new r("MISSING_RECIPIENT","recipient address is required");if(!e.claimAddress)throw new r("MISSING_CLAIM_ADDRESS","claim address is required");if(!e.secret)throw new r("MISSING_SECRET","secret is required");if(!e.claimKey)throw new r("MISSING_CLAIM_KEY","claim key is required");let t=await this.chain.getDrop(e.claimAddress);if(!t)throw new r("DROP_NOT_FOUND",`No drop for claim address ${e.claimAddress}`);let n=await this.chain.getClaimDigest({recipient:e.recipient,id:t.id,salt:t.salt}),a=await this.signer.signDigest({claimKey:e.claimKey,digest:n});return{txHash:await this.chain.withdraw({claimKey:e.claimKey,recipient:e.recipient,id:t.id,salt:t.salt,signature:a,secret:e.secret})}}};var S=class{constructor(e){this.chain=e}async execute(e){if(!e.claimAddress)throw new r("MISSING_CLAIM_ADDRESS","claim address is required");return{txHash:await this.chain.refund({claimAddress:e.claimAddress})}}};function p(i,e){if(!i)throw new r("API_ERROR",`SafeDropApiPort.${e} is not implemented by the injected api adapter`);return i}function J(i,e){let t=new u(e.chain,e.api,e.crypto,i.claimBaseUrl),n=new C(e.chain,e.signer),a=new S(e.chain),c=new f(e.chain,e.api,e.crypto),{api:s}=e;return{config:i,deposit:o=>t.execute(o),retrieveClaimKey:o=>s.retrieveClaimKey(o),withdraw:o=>n.execute(o),refund:o=>a.execute(o),getDrop:o=>e.chain.getDrop(o),listDrops:o=>p(s.listDrops,"listDrops").call(s,o),listEnvelopes:o=>p(s.listEnvelopes,"listEnvelopes").call(s,o),getLeaderboard:o=>p(s.getLeaderboard,"getLeaderboard").call(s,o),listHistories:o=>p(s.listHistories,"listHistories").call(s,o),getXConnection:()=>p(s.getXConnection,"getXConnection").call(s),connectX:o=>p(s.connectX,"connectX").call(s,o),disconnectX:()=>p(s.disconnectX,"disconnectX").call(s),batchClaim:o=>c.execute(o),requestBatchClaimSignature:o=>p(s.requestBatchClaimSignature,"requestBatchClaimSignature").call(s,o)}}export{r as a,m as b,w as c,b as d,O as e,T as f,u as g,f as h,C as i,S as j,J as k};
@@ -0,0 +1,516 @@
1
+ /** 0x-prefixed 주소. viem Address에 의존하지 않는 도메인 표현. */
2
+ type Address = string;
3
+ /** 0x-prefixed hex 문자열 (서명/salt/digest/해시 등). */
4
+ type Hex = string;
5
+ /** 소셜 식별자 string (컨트랙트 `id`). 예: 'theo_13303'. */
6
+ type Id = string;
7
+ /** 평문 비밀 문구. 어떤 서버에도 전송되지 않는다(링크 fragment 전용). */
8
+ type Secret = string;
9
+ /** keccak256(utf8(secret)). 컨트랙트에 기록되는 유일한 secret 파생값. */
10
+ type SecretHash = Hex;
11
+ type SocialProvider = 'x' | (string & {});
12
+ interface SocialIdentifier {
13
+ readonly provider: SocialProvider;
14
+ /** 소셜 핸들 = 컨트랙트 id. 정규화는 앱/어댑터 책임(deposit·API에 동일 값 사용). */
15
+ readonly handle: string;
16
+ }
17
+ interface OAuthProof {
18
+ provider: SocialProvider;
19
+ /** OAuth access token — 백엔드가 소셜 API로 핸들 소유를 검증. */
20
+ accessToken: string;
21
+ }
22
+ interface DepositParams {
23
+ /** 송금자 = sponsor = msg.sender (연결된 지갑). */
24
+ sender: Address;
25
+ /** 수령 대상 소셜 계정. handle이 컨트랙트 id가 된다. */
26
+ recipient: SocialIdentifier;
27
+ /** ERC20 토큰 주소. (계약은 ERC20 전용 — 네이티브 미지원.) */
28
+ token: Address;
29
+ /** raw 토큰 수량 (wei-scale). BigInt 유지. */
30
+ amount: bigint;
31
+ /** 비밀 문구. 미지정 시 CryptoPort.randomSecret()로 생성. (expiry는 계약이 결정.) */
32
+ secret?: Secret;
33
+ /**
34
+ * 수령인에게 보여줄 메시지 (최대 140 runes). 컨트랙트에는 필드가 없고
35
+ * 백엔드에만 저장된다 — 온체인 데이터가 아니다.
36
+ */
37
+ message?: string;
38
+ /** 카드 디자인 id. 목록은 `listEnvelopes()` (GET /envelopes). */
39
+ envelopeId?: string;
40
+ }
41
+ interface DepositResult {
42
+ /** 컨트랙트에 커밋된 임시 claim 지갑 주소 = 이 드롭의 키. */
43
+ readonly claimAddress: Address;
44
+ readonly secretHash: SecretHash;
45
+ /** 수령 링크 — secret은 URL fragment(#)에만 담긴다. */
46
+ readonly withdrawLink: string;
47
+ readonly depositTxHash: Hex;
48
+ }
49
+ interface WithdrawParams {
50
+ /** 수령 주소 — 수령인 본인이 지정. 서명에 바인딩된다. */
51
+ recipient: Address;
52
+ /** X OAuth 검증 후 백엔드에서 받은 임시 claim 지갑 주소. drops() 조회 키. */
53
+ claimAddress: Address;
54
+ /** 링크 fragment에서 얻은 secret 원문. */
55
+ secret: Secret;
56
+ /** 임시 claim 개인키 — 서명 + (스폰서드) withdraw tx 전송에 사용. */
57
+ claimKey: Hex;
58
+ }
59
+ interface WithdrawResult {
60
+ readonly txHash: Hex;
61
+ }
62
+ interface RefundParams {
63
+ /** 환불할 드롭의 claimAddr. 만료 후 sponsor(msg.sender)만 호출 가능. */
64
+ claimAddress: Address;
65
+ }
66
+ interface RefundResult {
67
+ readonly txHash: Hex;
68
+ }
69
+ interface BatchClaimParams {
70
+ /** 소셜 식별자(내 핸들). 내부에서 normalizeHandle을 거친다. */
71
+ id: Id;
72
+ /** 핸들 소유 증명용 X OAuth 토큰. 서명 발급의 전제. */
73
+ oauth: OAuthProof;
74
+ /**
75
+ * 기대하는 수령 주소. 서명은 **SIWE 인증 주소**에 바인딩되므로, 다르면
76
+ * `SIGN_FAILED`로 끊는다(다른 주소로 받으려면 그 주소로 SIWE 로그인).
77
+ * 미지정 시 백엔드가 정한 주소를 그대로 쓴다.
78
+ */
79
+ recipient?: Address;
80
+ /** unix seconds. 미지정 시 now + deadlineTtlSeconds. */
81
+ deadline?: bigint;
82
+ /** deadline 자동 계산용 TTL(초). 기본 1800. */
83
+ deadlineTtlSeconds?: bigint;
84
+ /** tx 1건이 처리할 드롭 수(서명 대상 아님 — 페이지 크기). 기본 30. */
85
+ maxCount?: number;
86
+ }
87
+ interface BatchClaimResult {
88
+ /** 페이지별 tx hash. 드롭이 maxCount 이하면 1건. */
89
+ readonly txHashes: readonly Hex[];
90
+ /** 실제로 서명에 바인딩된 수령 주소. */
91
+ readonly recipient: Address;
92
+ readonly id: Id;
93
+ readonly nonce: bigint;
94
+ readonly deadline: bigint;
95
+ }
96
+ type SafeDropErrorCode = 'MISSING_SENDER' | 'MISSING_RECIPIENT' | 'MISSING_SECRET' | 'MISSING_CLAIM_KEY' | 'MISSING_CLAIM_ADDRESS' | 'INVALID_AMOUNT' | 'INVALID_TOKEN' | 'DROP_NOT_FOUND' | 'SECRET_MISMATCH' | 'SIGN_FAILED' | 'CHAIN_ERROR' | 'API_ERROR' | 'INVALID_PARAM' | 'RATE_LIMITED' | 'INVALID_OAUTH_TOKEN' | 'UNAUTHORIZED' | 'WALLET_NOT_FOUND' | 'SEND_BLOCKED' | 'ENVELOPE_NOT_FOUND' | 'PENDING_LIMIT_EXCEEDED' | 'X_CONNECTION_NOT_FOUND';
97
+ declare class SafeDropError extends Error {
98
+ readonly code: SafeDropErrorCode;
99
+ readonly details?: Record<string, unknown>;
100
+ constructor(code: SafeDropErrorCode, message: string, details?: Record<string, unknown>);
101
+ }
102
+
103
+ /** GET/POST /x-connections — X 계정 ↔ 지갑 매핑 (최신 연결이 이긴다). */
104
+ interface XConnection {
105
+ readonly xUserId: string;
106
+ readonly handle: string;
107
+ readonly displayName: string;
108
+ readonly profileImageUrl: string;
109
+ /** SIWE 인증 주소. 본문으로 고를 수 없다. */
110
+ readonly walletAddress: Address;
111
+ }
112
+ /** 드롭을 보낸 sponsor. X 연결이 없으면 handle/displayName/profileImageUrl은 빈 문자열. */
113
+ interface DropSender {
114
+ readonly address: Address;
115
+ readonly handle: string;
116
+ readonly displayName: string;
117
+ readonly profileImageUrl: string;
118
+ }
119
+ /** GET /drops의 개별 항목. claimAddress로 개별 수령(withdraw)을 진행한다. */
120
+ interface PendingDrop {
121
+ readonly claimAddress: Address;
122
+ readonly token: Address;
123
+ /** raw 토큰 수량 (wei-scale). */
124
+ readonly amount: bigint;
125
+ /** sender가 남긴 메시지. 없으면 빈 문자열. */
126
+ readonly message: string;
127
+ /** 카드 디자인 id (GET /envelopes). 없으면 빈 문자열. */
128
+ readonly envelopeId: string;
129
+ /** RFC3339 문자열. */
130
+ readonly depositedAt: string;
131
+ readonly sender: DropSender;
132
+ }
133
+ /** GET /drops — 수령 대기 인박스 (Bearer JWT + 활성 X 연결 필요). */
134
+ interface DropInbox {
135
+ readonly items: readonly PendingDrop[];
136
+ readonly count: number;
137
+ /** pending 합계 (wei-scale). */
138
+ readonly totalAmount: bigint;
139
+ /** 첫 수령은 개별 claim만 허용 — 일괄 수령 CTA 게이트. */
140
+ readonly hasClaimedBefore: boolean;
141
+ }
142
+ /** GET /envelopes — 카드 디자인. 공개 엔드포인트. */
143
+ interface Envelope {
144
+ readonly id: string;
145
+ readonly name: string;
146
+ readonly imageUrl: string;
147
+ readonly badge: string;
148
+ readonly sortOrder: number;
149
+ }
150
+ /** GET /leaderboards — identifier별 pending 잔액 상위(최대 10). 공개 엔드포인트. */
151
+ interface LeaderboardEntry {
152
+ readonly identifier: string;
153
+ /** 수령 가능(pending) 합계 (wei-scale). */
154
+ readonly balance: bigint;
155
+ /** 최근 Deposited 금액 (wei-scale). */
156
+ readonly lastDepositAmount: bigint;
157
+ }
158
+ type PopEventName = 'Deposited' | 'Withdrawn' | 'Refunded' | 'BatchWithdrawn';
159
+ /** GET /histories 항목. 이벤트가 담지 않는 주소는 null. */
160
+ interface HistoryEntry {
161
+ readonly eventName: PopEventName | (string & {});
162
+ readonly txHash: Hex;
163
+ readonly blockNumber: number;
164
+ readonly logIndex: number;
165
+ readonly contractAddress: Address;
166
+ readonly fromAddress: Address | null;
167
+ readonly toAddress: Address | null;
168
+ readonly tempTo: Address | null;
169
+ readonly identifier: string;
170
+ /** wei-scale. */
171
+ readonly amount: bigint;
172
+ readonly createdAt: string;
173
+ readonly payload?: Record<string, unknown>;
174
+ }
175
+ interface HistoryPage {
176
+ readonly items: readonly HistoryEntry[];
177
+ /** 1-based. */
178
+ readonly page: number;
179
+ readonly pageSize: number;
180
+ readonly total: number;
181
+ }
182
+ /**
183
+ * POST /batch-claim-signature 결과 — EIP-712 ValidatorClaim 서명.
184
+ * recipient는 항상 Bearer 토큰의 SIWE 주소다(본문으로 못 고른다).
185
+ * 같은 id의 live 드롭이 모두 빠질 때까지 페이지네이션된 온체인 호출에
186
+ * **재사용**한다 — 페이지마다 다시 발급받지 않는다.
187
+ */
188
+ interface BatchClaimSignature {
189
+ readonly id: Id;
190
+ readonly recipient: Address;
191
+ readonly nonce: bigint;
192
+ readonly deadline: bigint;
193
+ readonly signature: Hex;
194
+ }
195
+
196
+ /**
197
+ * 순수 계산이 아니라 외부 구현(해시 라이브러리/CSPRNG)이 필요한 암호 연산.
198
+ * core는 keccak/난수 구현을 갖지 않으므로 Port로 분리한다.
199
+ * 어댑터가 viem `keccak256`/`crypto.getRandomValues` 등으로 구현.
200
+ */
201
+ interface CryptoPort {
202
+ /** utf8(secret) → keccak256 → 0x-prefixed hex. 컨트랙트 secretHash와 일치해야 함. */
203
+ keccak256(input: Secret): SecretHash;
204
+ /** CSPRNG 기반 고엔트로피 secret 문구 생성. */
205
+ randomSecret(): Secret;
206
+ /**
207
+ * digest + 서명 → 서명자 주소. 백엔드 validator 서명을 **가스 쓰기 전에** 검증하는
208
+ * 용도(최종 관문은 여전히 컨트랙트다). optional — 미구현 어댑터에서는 사전 검증만
209
+ * 생략되고 플로우는 그대로 동작한다.
210
+ */
211
+ recoverAddress?(params: {
212
+ digest: Hex;
213
+ signature: Hex;
214
+ }): Promise<Address>;
215
+ }
216
+
217
+ /**
218
+ * off-chain 백엔드(one-pop-api) 계약. 임시 claim 키 보관/전달, X 연결, 조회
219
+ * 엔드포인트, validator 서명을 담당한다. 이 포트만으로는 자금을 옮길 수 없다
220
+ * (secret 부재) — 신뢰 분리의 한 축.
221
+ * 스펙: packages/pop/src/infrastructure/openapi.json
222
+ *
223
+ * `createClaimWallet`/`retrieveClaimKey`만 필수다(deposit·withdraw 흐름). 나머지는
224
+ * optional — 기존 mock/커스텀 구현이 깨지지 않게 하고, 미구현 어댑터에서는
225
+ * facade가 `API_ERROR`로 명시적으로 실패한다. HttpSafeDropApiAdapter는 전부 구현한다.
226
+ */
227
+ interface SafeDropApiPort {
228
+ /** Deposit: 수령 identifier에 대한 임시 claim 지갑 생성 → 커밋할 주소 반환. */
229
+ createClaimWallet(params: {
230
+ sender: Address;
231
+ recipient: SocialIdentifier;
232
+ /** 수령인에게 보여줄 메시지 (≤140 runes, 백엔드 저장 전용). */
233
+ message?: string;
234
+ /** 카드 디자인 id (GET /envelopes). */
235
+ envelopeId?: string;
236
+ }): Promise<{
237
+ claimAddress: Address;
238
+ }>;
239
+ /**
240
+ * Withdraw: X OAuth 검증 후 복호화된 임시 claim 개인키 반환 (point of return).
241
+ * dropId가 아니라 (sender_address + OAuth 토큰 소유자의 identifier) 조합으로
242
+ * 해당하는 최신 pending 지갑 키를 찾는다 — one-pop-api /wallets/private-key.
243
+ *
244
+ * ⚠️ 한 (sender, identifier)에 pending 드롭이 여러 개 있을 수 있다(백엔드가
245
+ * 중복 제한을 풀었다). 이 엔드포인트는 그중 **최신 1건**만 돌려주고 특정
246
+ * claimAddress를 고르는 파라미터가 없다 — 같은 sender가 보낸 여러 드롭은
247
+ * 링크마다 순차로 수령해야 한다. 전체 목록은 `listDrops()`로 본다.
248
+ */
249
+ retrieveClaimKey(params: {
250
+ senderAddress: Address;
251
+ oauth: OAuthProof;
252
+ }): Promise<{
253
+ claimKey: Hex;
254
+ claimAddress: Address;
255
+ }>;
256
+ /** GET /drops — 내게 온 수령 대기 목록. Bearer JWT + 활성 X 연결 필요. */
257
+ listDrops?(params?: {
258
+ token?: Address;
259
+ }): Promise<DropInbox>;
260
+ /** GET /envelopes — 카드 디자인 목록. 공개(인증 불필요). */
261
+ listEnvelopes?(params?: {
262
+ locale?: string;
263
+ }): Promise<readonly Envelope[]>;
264
+ /** GET /leaderboards — 토큰별 pending 잔액 상위. 공개(인증 불필요). */
265
+ getLeaderboard?(params: {
266
+ token: Address;
267
+ }): Promise<readonly LeaderboardEntry[]>;
268
+ /** GET /histories — 내가 sender이거나 최종 수령인인 온체인 이벤트. Bearer JWT. */
269
+ listHistories?(params?: {
270
+ event?: PopEventName;
271
+ /** 1-based (기본 1). */
272
+ page?: number;
273
+ /** 기본 20, 최대 100. */
274
+ pageSize?: number;
275
+ }): Promise<HistoryPage>;
276
+ /** GET /x-connections — 활성 X 연결. 없으면 null(404를 에러로 던지지 않는다). */
277
+ getXConnection?(): Promise<XConnection | null>;
278
+ /** POST /x-connections — X 계정을 SIWE 지갑에 연결(최신 연결이 이긴다). */
279
+ connectX?(params: {
280
+ oauth: OAuthProof;
281
+ }): Promise<XConnection>;
282
+ /** DELETE /x-connections — 연결 해제. 이미 없으면 조용히 성공(idempotent). */
283
+ disconnectX?(): Promise<void>;
284
+ /**
285
+ * POST /batch-claim-signature — 일괄 수령용 EIP-712 ValidatorClaim 서명.
286
+ * nonce/deadline은 호출자가 정한다. nonce는 컨트랙트
287
+ * `validatorNonceById(keccak256(utf8(id)))` — **id 해시**로 키잉된다(openapi 설명은
288
+ * string처럼 읽히지만 배포본은 bytes32다). 조립은 `BatchClaimUseCase`가 담당하니
289
+ * 앱은 보통 `safeDrop.batchClaim()`만 쓰면 된다.
290
+ */
291
+ requestBatchClaimSignature?(params: {
292
+ id: Id;
293
+ oauth: OAuthProof;
294
+ nonce: bigint;
295
+ deadline: bigint;
296
+ }): Promise<BatchClaimSignature>;
297
+ }
298
+
299
+ /**
300
+ * drops(claimAddr) 결과 매핑.
301
+ *
302
+ * ⚠️ 배포본마다 `drops`의 반환 필드 수가 다르다(permit 배포본은 5개 —
303
+ * sponsor/amount/secretHash/salt/id). 그래서 `token`·`expiry`는 **optional**이다:
304
+ * - `token` — 5필드 배포본에서는 에스크로의 `token()`으로 채운다(단일 토큰 고정).
305
+ * - `expiry` — 5필드 배포본은 노출하지 않는다 → `undefined`. 만료 판정에 쓰기 전에
306
+ * 존재 여부를 확인해야 한다(없다고 만료가 아니라, 알 수 없다는 뜻).
307
+ */
308
+ interface DropState {
309
+ claimAddress: Address;
310
+ sponsor: Address;
311
+ token?: Address;
312
+ amount: bigint;
313
+ expiry?: number;
314
+ secretHash: SecretHash;
315
+ salt: Hex;
316
+ id: Id;
317
+ }
318
+ /**
319
+ * SafeDrop 컨트랙트 상호작용. 어댑터는 자신의 컨트랙트 주소를 알고 있으므로
320
+ * approve의 spender 등은 받지 않는다. 쓰기는 receipt 확인 후 tx hash 반환.
321
+ * withdraw는 임시 지갑이 직접 전송(가스 대납) — claimKey 필요.
322
+ * ABI: src/infrastructure/safedrop_abi.json.
323
+ */
324
+ interface SafeDropChainPort {
325
+ /** drops(claimAddr). 없으면 null. */
326
+ getDrop(claimAddress: Address): Promise<DropState | null>;
327
+ /** claimDigest(recipient, id, salt) — 서명 대상 bytes32. */
328
+ getClaimDigest(params: {
329
+ recipient: Address;
330
+ id: Id;
331
+ salt: Hex;
332
+ }): Promise<Hex>;
333
+ /** ERC20 approve (spender = SafeDrop 컨트랙트). 연결된 지갑이 서명. */
334
+ approve(params: {
335
+ token: Address;
336
+ amount: bigint;
337
+ }): Promise<Hex>;
338
+ /** deposit(claimAddr, token, amount, id, secretHash). sponsor = msg.sender. */
339
+ deposit(params: {
340
+ claimAddress: Address;
341
+ token: Address;
342
+ amount: bigint;
343
+ id: Id;
344
+ secretHash: SecretHash;
345
+ }): Promise<{
346
+ txHash: Hex;
347
+ }>;
348
+ /**
349
+ * EIP-2612 permit 예치 — approve tx 없이 서명 1회 + tx 1회로 끝낸다.
350
+ * permit 예치를 지원하는 배포본에서만 구현되는 optional 경로: 존재하면
351
+ * DepositUseCase가 approve+deposit 대신 이걸 쓴다.
352
+ * 서명(typed data)·deadline·v/r/s는 전부 어댑터 내부 관심사다.
353
+ */
354
+ depositWithPermit?(params: {
355
+ claimAddress: Address;
356
+ token: Address;
357
+ amount: bigint;
358
+ id: Id;
359
+ secretHash: SecretHash;
360
+ }): Promise<{
361
+ txHash: Hex;
362
+ }>;
363
+ /**
364
+ * withdraw(recipient, id, salt, signature, secret). claimAddr는 서명 recover로
365
+ * 도출. tx는 claimKey 임시 지갑이 직접 전송 — estimateGas 없이 고정 gas +
366
+ * dynamic fee(type-2)로 보내 컨트랙트 gas-abstraction 대납을 받는다.
367
+ */
368
+ withdraw(params: {
369
+ claimKey: Hex;
370
+ recipient: Address;
371
+ id: Id;
372
+ salt: Hex;
373
+ signature: Hex;
374
+ secret: Secret;
375
+ }): Promise<Hex>;
376
+ /** refund(claimAddr). 만료 후 sponsor(msg.sender)만. */
377
+ refund(params: {
378
+ claimAddress: Address;
379
+ }): Promise<Hex>;
380
+ /** validatorNonceById(keccak256(utf8(id))). id 해싱은 어댑터 책임. */
381
+ getValidatorNonce?(params: {
382
+ id: Id;
383
+ }): Promise<bigint>;
384
+ /**
385
+ * validatorClaimDigest(recipient, id, nonce, deadline) — 온체인 EIP-712 다이제스트.
386
+ * 로컬 재조립 대신 이 값을 읽어 백엔드 서명을 검증한다.
387
+ */
388
+ getValidatorClaimDigest?(params: {
389
+ recipient: Address;
390
+ id: Id;
391
+ nonce: bigint;
392
+ deadline: bigint;
393
+ }): Promise<Hex>;
394
+ /** validator() — 서명자여야 하는 주소. 백엔드 서명 검증의 기준값. */
395
+ getValidator?(): Promise<Address>;
396
+ /** liveDropCountById(id) — 남은 수령 대기 건수(페이지네이션 종료 조건). */
397
+ getLiveDropCount?(params: {
398
+ id: Id;
399
+ }): Promise<bigint>;
400
+ /**
401
+ * claimedRecipientOf(id) — 이 id가 **개별 수령(secret+OAuth)으로 확정한** 수령 주소.
402
+ * 미수령 id는 zero. 일괄 수령의 목적지를 온체인 값으로 교차 검증하는 데 쓴다.
403
+ */
404
+ getClaimedRecipient?(params: {
405
+ id: Id;
406
+ }): Promise<Address>;
407
+ /**
408
+ * withdrawByValidator(recipient, id, nonce, deadline, maxCount, signature).
409
+ * tx는 **수령인 본인 지갑**이 보낸다(컨트랙트가 msg.sender와 서명의 recipient를
410
+ * 대조). `maxCount`는 서명 대상이 아니라 페이지 크기다 — 같은 서명을 재사용한다.
411
+ */
412
+ withdrawByValidator?(params: {
413
+ recipient: Address;
414
+ id: Id;
415
+ nonce: bigint;
416
+ deadline: bigint;
417
+ maxCount: number;
418
+ signature: Hex;
419
+ }): Promise<Hex>;
420
+ }
421
+
422
+ /**
423
+ * 임시 claim 키로 claimDigest(온체인에서 읽은 bytes32)에 raw ECDSA 서명을 만든다.
424
+ * 컨트랙트가 claimDigest를 계산해주므로 로컬 EIP-712 typed data 구성은 불필요 —
425
+ * digest를 그대로 서명한다. 서명은 브라우저 로컬, 백엔드 미관여.
426
+ */
427
+ interface ClaimSignerPort {
428
+ signDigest(params: {
429
+ claimKey: Hex;
430
+ digest: Hex;
431
+ }): Promise<Hex>;
432
+ }
433
+
434
+ /**
435
+ * 프레임워크 무관 진입점. 포트 구현(어댑터)을 주입하면 usecase를 조립해 반환한다.
436
+ *
437
+ * const safeDrop = createSafeDrop(
438
+ * { claimBaseUrl },
439
+ * { api, chain, signer, crypto },
440
+ * );
441
+ * const { withdrawLink, claimAddress } = await safeDrop.deposit({ ... });
442
+ *
443
+ * viem/HTTP 어댑터를 자동 조립하려면 `@nexus-cross/pop/adapters`의
444
+ * createSafeDropClient를 사용한다. (도메인/컨트랙트 주소는 chain 어댑터가 안다.)
445
+ */
446
+
447
+ interface SafeDropConfig {
448
+ /** 수령 링크 base URL. 예: https://one-pop.example/claim */
449
+ claimBaseUrl: string;
450
+ }
451
+ interface SafeDropDeps {
452
+ api: SafeDropApiPort;
453
+ chain: SafeDropChainPort;
454
+ signer: ClaimSignerPort;
455
+ crypto: CryptoPort;
456
+ }
457
+ interface SafeDrop {
458
+ readonly config: SafeDropConfig;
459
+ deposit(params: DepositParams): Promise<DepositResult>;
460
+ /** X OAuth 검증 후 (sender_address + 토큰 소유자)로 임시 claim 키/주소를 받는다 (point of return). */
461
+ retrieveClaimKey(params: {
462
+ senderAddress: Address;
463
+ oauth: OAuthProof;
464
+ }): Promise<{
465
+ claimKey: Hex;
466
+ claimAddress: Address;
467
+ }>;
468
+ withdraw(params: WithdrawParams): Promise<WithdrawResult>;
469
+ refund(params: RefundParams): Promise<RefundResult>;
470
+ /** 온체인 드롭 상태 (drops(claimAddr)). 없으면 null. */
471
+ getDrop(claimAddress: Address): Promise<DropState | null>;
472
+ /** 내게 온 수령 대기 목록. Bearer JWT + 활성 X 연결 필요. */
473
+ listDrops(params?: {
474
+ token?: Address;
475
+ }): Promise<DropInbox>;
476
+ /** 카드 디자인 목록 (공개). deposit의 `envelopeId`에 쓴다. */
477
+ listEnvelopes(params?: {
478
+ locale?: string;
479
+ }): Promise<readonly Envelope[]>;
480
+ /** 토큰별 pending 잔액 상위 (공개). */
481
+ getLeaderboard(params: {
482
+ token: Address;
483
+ }): Promise<readonly LeaderboardEntry[]>;
484
+ /** 내 온체인 이벤트 히스토리. Bearer JWT 필요. */
485
+ listHistories(params?: {
486
+ event?: PopEventName;
487
+ page?: number;
488
+ pageSize?: number;
489
+ }): Promise<HistoryPage>;
490
+ /** 활성 X 연결. 없으면 null. */
491
+ getXConnection(): Promise<XConnection | null>;
492
+ /** X 계정을 SIWE 지갑에 연결. `listDrops`의 선행 조건. */
493
+ connectX(params: {
494
+ oauth: OAuthProof;
495
+ }): Promise<XConnection>;
496
+ /** X 연결 해제 (idempotent). */
497
+ disconnectX(): Promise<void>;
498
+ /**
499
+ * 일괄 수령 — nonce 조회 → 백엔드 서명 → **가스 전 서명 검증** → 전량 회수까지
500
+ * 한 번에. tx는 연결된 수령인 지갑이 보낸다(secret 불필요).
501
+ */
502
+ batchClaim(params: BatchClaimParams): Promise<BatchClaimResult>;
503
+ /**
504
+ * 서명만 따로 받는 저수준 경로(직접 컨트랙트를 호출할 때). 대부분은 `batchClaim`을 쓴다.
505
+ * nonce는 `validatorNonceById(keccak256(utf8(id)))`에서 읽은 값이어야 한다.
506
+ */
507
+ requestBatchClaimSignature(params: {
508
+ id: Id;
509
+ oauth: OAuthProof;
510
+ nonce: bigint;
511
+ deadline: bigint;
512
+ }): Promise<BatchClaimSignature>;
513
+ }
514
+ declare function createSafeDrop(config: SafeDropConfig, deps: SafeDropDeps): SafeDrop;
515
+
516
+ export { type Address as A, type BatchClaimParams as B, type CryptoPort as C, type DepositParams as D, type Envelope as E, type Hex as H, type Id as I, type LeaderboardEntry as L, type OAuthProof as O, type PendingDrop as P, type RefundParams as R, type SafeDropChainPort as S, type WithdrawParams as W, type XConnection as X, type SafeDropApiPort as a, type DepositResult as b, type BatchClaimResult as c, type ClaimSignerPort as d, type WithdrawResult as e, type RefundResult as f, type Secret as g, type BatchClaimSignature as h, type DropInbox as i, type DropSender as j, type DropState as k, type HistoryEntry as l, type HistoryPage as m, type PopEventName as n, type SafeDrop as o, type SafeDropConfig as p, type SafeDropDeps as q, SafeDropError as r, type SafeDropErrorCode as s, type SecretHash as t, type SocialIdentifier as u, type SocialProvider as v, createSafeDrop as w };