diff --git a/cmd/faucet.superposition/graph/schema.resolvers.go b/cmd/faucet.superposition/graph/schema.resolvers.go index a025b1e6..2937f009 100644 --- a/cmd/faucet.superposition/graph/schema.resolvers.go +++ b/cmd/faucet.superposition/graph/schema.resolvers.go @@ -16,8 +16,8 @@ import ( ) // RequestTokens is the resolver for the requestTokens field. -func (r *mutationResolver) RequestTokens(ctx context.Context, wallet_ string, turnstileToken string) (string, error) { - wallet := strings.ToLower(wallet_) +func (r *mutationResolver) RequestTokens(ctx context.Context, wallet string, turnstileToken string) (string, error) { + wallet = strings.ToLower(wallet) // Get the user's IP address to prevent them from spamming this // incase our rate limiting is skipped somehow (it's good to be cautious.) ipAddrs, _ := ctx.Value("X-Forwarded-For").(string) diff --git a/cmd/graphql.ethereum/graph/generated.go b/cmd/graphql.ethereum/graph/generated.go index 578b4f3a..e0d9e6ec 100644 --- a/cmd/graphql.ethereum/graph/generated.go +++ b/cmd/graphql.ethereum/graph/generated.go @@ -167,6 +167,7 @@ type ComplexityRoot struct { SeawaterPosition struct { Created func(childComplexity int) int ID func(childComplexity int) int + IsVested func(childComplexity int) int Liquidity func(childComplexity int) int Lower func(childComplexity int) int Owner func(childComplexity int) int @@ -316,6 +317,7 @@ type SeawaterPositionResolver interface { Pool(ctx context.Context, obj *seawater.Position) (seawater.Pool, error) Lower(ctx context.Context, obj *seawater.Position) (int, error) Upper(ctx context.Context, obj *seawater.Position) (int, error) + IsVested(ctx context.Context, obj *seawater.Position) (bool, error) Liquidity(ctx context.Context, obj *seawater.Position) (model.PairAmount, error) } type SeawaterPositionsGlobalResolver interface { @@ -941,6 +943,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.SeawaterPosition.ID(childComplexity), true + case "SeawaterPosition.isVested": + if e.complexity.SeawaterPosition.IsVested == nil { + break + } + + return e.complexity.SeawaterPosition.IsVested(childComplexity), true + case "SeawaterPosition.liquidity": if e.complexity.SeawaterPosition.Liquidity == nil { break @@ -1875,6 +1884,11 @@ type SeawaterPosition { """ upper: Int! + """ + True if this position is currently vested in Leo, false otherwise. + """ + isVested: Boolean! + """ Liquidity available in this specific position. """ @@ -4477,6 +4491,8 @@ func (ec *executionContext) fieldContext_Query_getPosition(ctx context.Context, return ec.fieldContext_SeawaterPosition_lower(ctx, field) case "upper": return ec.fieldContext_SeawaterPosition_upper(ctx, field) + case "isVested": + return ec.fieldContext_SeawaterPosition_isVested(ctx, field) case "liquidity": return ec.fieldContext_SeawaterPosition_liquidity(ctx, field) } @@ -6871,6 +6887,50 @@ func (ec *executionContext) fieldContext_SeawaterPosition_upper(_ context.Contex return fc, nil } +func (ec *executionContext) _SeawaterPosition_isVested(ctx context.Context, field graphql.CollectedField, obj *seawater.Position) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_SeawaterPosition_isVested(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.SeawaterPosition().IsVested(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_SeawaterPosition_isVested(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "SeawaterPosition", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _SeawaterPosition_liquidity(ctx context.Context, field graphql.CollectedField, obj *seawater.Position) (ret graphql.Marshaler) { fc, err := ec.fieldContext_SeawaterPosition_liquidity(ctx, field) if err != nil { @@ -7022,6 +7082,8 @@ func (ec *executionContext) fieldContext_SeawaterPositionsGlobal_positions(_ con return ec.fieldContext_SeawaterPosition_lower(ctx, field) case "upper": return ec.fieldContext_SeawaterPosition_upper(ctx, field) + case "isVested": + return ec.fieldContext_SeawaterPosition_isVested(ctx, field) case "liquidity": return ec.fieldContext_SeawaterPosition_liquidity(ctx, field) } @@ -7244,6 +7306,8 @@ func (ec *executionContext) fieldContext_SeawaterPositionsUser_positions(_ conte return ec.fieldContext_SeawaterPosition_lower(ctx, field) case "upper": return ec.fieldContext_SeawaterPosition_upper(ctx, field) + case "isVested": + return ec.fieldContext_SeawaterPosition_isVested(ctx, field) case "liquidity": return ec.fieldContext_SeawaterPosition_liquidity(ctx, field) } @@ -12990,6 +13054,42 @@ func (ec *executionContext) _SeawaterPosition(ctx context.Context, sel ast.Selec continue } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "isVested": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._SeawaterPosition_isVested(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "liquidity": field := field diff --git a/cmd/graphql.ethereum/graph/schema.resolvers.go b/cmd/graphql.ethereum/graph/schema.resolvers.go index 89ab7193..b28242e1 100644 --- a/cmd/graphql.ethereum/graph/schema.resolvers.go +++ b/cmd/graphql.ethereum/graph/schema.resolvers.go @@ -293,7 +293,7 @@ func (r *queryResolver) GetPoolPositions(ctx context.Context, pool string, first positions = model.SeawaterPositionsGlobal(MockGetPoolPositions(p)) return } - stmt := r.DB.Table("seawater_active_positions_2"). + stmt := r.DB.Table("seawater_active_positions_3"). Where("pool = ?", p). Limit(*first). Order("created_by desc") @@ -330,7 +330,7 @@ func (r *queryResolver) GetPosition(ctx context.Context, id int) (position *seaw position = MockGetPosition(id) return } - err = r.DB.Table("seawater_positions_1"). + err = r.DB.Table("seawater_positions_2"). Where("pos_id = ?", id). Scan(&position). Error @@ -355,7 +355,7 @@ func (r *queryResolver) GetPositions(ctx context.Context, wallet string, first * ) return } - stmt := r.DB.Table("seawater_active_positions_2"). + stmt := r.DB.Table("seawater_active_positions_3"). Where("owner = ?", w). Limit(*first). Order("created_by desc") @@ -937,7 +937,7 @@ func (r *seawaterPoolResolver) Positions(ctx context.Context, obj *seawater.Pool positions = model.SeawaterPositionsGlobal(MockGetPoolPositions(obj.Token)) return } - stmt := r.DB.Table("seawater_active_positions_2"). + stmt := r.DB.Table("seawater_active_positions_3"). Where("pool = ?", obj.Token). Limit(*first). Order("created_by desc") @@ -974,7 +974,7 @@ func (r *seawaterPoolResolver) PositionsForUser(ctx context.Context, obj *seawat positions = model.SeawaterPositionsUser(MockGetPoolPositions(w)) return } - err = r.DB.Table("seawater_active_positions_2"). + err = r.DB.Table("seawater_active_positions_3"). Where("pool = ? and owner = ?", obj.Token, wallet). Scan(&positions). Error @@ -1197,6 +1197,14 @@ func (r *seawaterPositionResolver) Upper(ctx context.Context, obj *seawater.Posi return int(obj.Upper.Int64()), nil } +// IsVested is the resolver for the isVested field. +func (r *seawaterPositionResolver) IsVested(ctx context.Context, obj *seawater.Position) (bool, error) { + if obj == nil { + return false, fmt.Errorf("no position obj") + } + return obj.IsVested, nil +} + // Liquidity is the resolver for the liquidity field. func (r *seawaterPositionResolver) Liquidity(ctx context.Context, obj *seawater.Position) (model.PairAmount, error) { if obj == nil { @@ -1333,7 +1341,7 @@ func (r *seawaterPositionsGlobalResolver) Next(ctx context.Context, obj *model.S to := time.Unix(int64(*obj.To), 0) // Start to construct a statement based on whether internally a // wallet, or a pool, was used. - stmt := r.DB.Table("seawater_active_positions_2"). + stmt := r.DB.Table("seawater_active_positions_3"). Where("created_by < ?", to). Limit(*first). Order("created_by desc") @@ -1441,7 +1449,7 @@ func (r *seawaterPositionsUserResolver) Next(ctx context.Context, obj *model.Sea to := time.Unix(int64(*obj.To), 0) // Start to construct a statement based on whether internally a // wallet, or a pool, was used. - stmt := r.DB.Table("seawater_active_positions_2"). + stmt := r.DB.Table("seawater_active_positions_3"). Where("created_by < ?", to). Limit(*first). Order("created_by desc") @@ -1677,7 +1685,7 @@ func (r *walletResolver) Positions(ctx context.Context, obj *model.Wallet, first ) return } - stmt := r.DB.Table("seawater_active_positions_2"). + stmt := r.DB.Table("seawater_active_positions_3"). Where("owner = ?", obj.Address). Limit(*first). Order("created_by desc") diff --git a/cmd/graphql.ethereum/schema.graphqls b/cmd/graphql.ethereum/schema.graphqls index 96d8216c..9e08063a 100644 --- a/cmd/graphql.ethereum/schema.graphqls +++ b/cmd/graphql.ethereum/schema.graphqls @@ -522,6 +522,11 @@ type SeawaterPosition { """ upper: Int! + """ + True if this position is currently vested in Leo, false otherwise. + """ + isVested: Boolean! + """ Liquidity available in this specific position. """ diff --git a/db/migrations/1725866798-seawater_positions_vested.sql b/db/migrations/1725866798-seawater_positions_vested.sql new file mode 100644 index 00000000..10012387 --- /dev/null +++ b/db/migrations/1725866798-seawater_positions_vested.sql @@ -0,0 +1,54 @@ +-- migrate:up + +-- seawater_positions_vested to determine whether positions are vested +-- in Leo by looking at their most recent vest/divest event +CREATE VIEW seawater_positions_vested AS SELECT DISTINCT ON (position_id) + is_vested, + position_id, + created_by +FROM ( + SELECT + TRUE AS is_vested, + position_id, + created_by + FROM + events_leo_positionvested + UNION + SELECT + FALSE AS is_vested, + position_id, + created_by + FROM + events_leo_positiondivested + ORDER BY + created_by DESC) a; + +CREATE VIEW seawater_positions_2 AS + SELECT + events_seawater_mintPosition.created_by AS created_by, + events_seawater_mintPosition.block_hash AS block_hash, + events_seawater_mintPosition.transaction_hash AS transaction_hash, + events_seawater_mintPosition.block_number AS created_block_number, + events_seawater_mintPosition.pos_id AS pos_id, + COALESCE(transfers.to_, events_seawater_mintPosition.owner) AS owner, + pool, + lower, + upper, + is_vested + FROM events_seawater_mintPosition + LEFT JOIN events_seawater_transferPosition AS transfers + ON transfers.pos_id = events_seawater_mintPosition.pos_id + LEFT JOIN seawater_positions_vested AS vested + ON vested.position_id = events_seawater_mintPosition.pos_id +; + +CREATE VIEW seawater_active_positions_3 AS +SELECT + seawater_active_positions_2.*, + COALESCE(is_vested, FALSE) AS is_vested +FROM + seawater_active_positions_2 + LEFT JOIN seawater_positions_vested ON seawater_active_positions_2.pos_id = seawater_positions_vested.position_id +; + +-- migrate:down diff --git a/lib/types/seawater/seawater.go b/lib/types/seawater/seawater.go index 4d28e084..033ba098 100644 --- a/lib/types/seawater/seawater.go +++ b/lib/types/seawater/seawater.go @@ -29,6 +29,7 @@ type Position struct { Pool types.Address `json:"pool"` Lower types.Number `json:"lower"` Upper types.Number `json:"upper"` + IsVested bool `json:"is_vested"` } // PositionSnapshot taken from snapshot_positions_log_1. Used to service